Commit Graph

508 Commits

Author SHA1 Message Date
Dudu-0223
fef4313685 test(spill-policy): guard loader export shape 2026-07-09 11:04:34 +08:00
Dudu-0223
c9310d2a19 fix: address codex review round 3
- spill-policy enforces the true cap invariant: it never emits a replacement
  larger than maxInlineBytes. When the notice alone exceeds the cap (tiny cap or
  long spill root) there is no within-cap replacement, so the inline result is
  kept — the previous guard only compared against the original size and could
  still return content over the cap for a large original. A within-cap
  replacement is always smaller than the original, so this subsumes the earlier
  check.
- Add the HMR-disposal test the conventions require for a new registration:
  dispose the plugin fiber and assert oversized results stop being transformed
  and nothing more is spilled (no leaked tools/post-execute listener on reload).
2026-07-09 10:07:07 +08:00
Dudu-0223
326b199f25 fix: address codex review round 2
- spill-policy reserves the spill notice's byte cost inside maxInlineBytes, so
  the replacement (preview + notice) never exceeds the documented model-facing
  cap. When the notice alone fills the budget the preview is empty; when even a
  notice-only replacement is not smaller than the original, the inline result is
  kept (spilling would only add bytes).
- retention TextRetainer trims an oversized single suffix chunk to the last
  suffixCap bytes on push, so tail/headTail retention stays bounded by suffixCap
  instead of retaining and re-copying the whole chunk in finish() — this is the
  spill preview path, which pushes the whole result as one chunk.
2026-07-09 09:51:35 +08:00
Dudu-0223
d0c2f0916d fix: address codex review round 1
- spill-policy validates maxInlineBytes as a non-negative integer at LOAD, so a
  bad config fails the deployment instead of letting a negative value reach
  TextRetainer and turn every oversized-result call into an isError.
- Document the spill seam vocabulary in docs/core-data-structures/spill.md
  (SaveTextSpill/SpillOwner/SpillSource/SpillRef/SpillPath, verbatim + type-equiv
  gated) and index it from core.md, matching the other capability seams.
2026-07-08 22:54:26 +08:00
Dudu-0223
463b72ce96 feat(spill): add tool-output spill seam, local backend, and policy
Oversized plain-text tool results now spill to a session-scoped file and
return a bounded preview plus the spill path, so a verbose result stays
readable via `read` without consuming the next model request in full.

- dsh-spill: minimal SpillFiles seam (saveText → session-scoped SpillPath)
- dsh-spill-local: private 0700 session dirs, traversal-safe names, exclusive
  owner-only writes
- dsh-spill-policy: tools/post-execute transformer; no-op unless maxInlineBytes
  is set; skips read; best-effort on save failure (never turns a success into
  an isError)

web_fetch is the showcase — no tool-specific spill code. The coding-agent
example loads the stack so its keyless Loader smoke guards the namespace-plugin
export shape. Snapshot gap for a transcript-visible web_fetch spill is recorded
in the RFC's Consequences (ACP replay is keyless and cannot hit the web).
2026-07-08 20:41:55 +08:00
Dudu-0223
4f2f34c6fd docs: BashRunResult timedOut/aborted are first-cause, not independent (codex round 3)
The timeout convergence on this branch made timedOut/aborted mutually
exclusive — one fused deadline reports the single cause that first cut the
command short — as the timeout-library RFC documents deliberately. The seam
type's JSDoc still described the old independent latches, so a consumer could
code the wrong contract. State first-cause classification on both fields and
cross-link the RFC. Docs-only; the code already matches.
2026-07-07 10:25:29 +08:00
Dudu-0223
f4acb1cf05 fix: report omitted bytes against retained text, not budget (codex round 2)
finish() derived the exact omitted count from the pre-trim prefix/suffix
budget, but the UTF-8 boundary trims drop additional partial-codepoint bytes,
so an "Omitted N bytes" notice overstated what was kept (head maxBytes:2 over
'a€b' returned 'a' but claimed exact 3 when 4 bytes are absent). Derive the
count from the bytes actually returned (total − keptPrefix − keptSuffix) so
exact metadata matches the text and atLeast stays a valid lower bound.
2026-07-07 10:01:03 +08:00
Dudu-0223
cbfdf9d092 fix: preserve a codepoint spanning the head|tail split (codex round 1)
When headTail budgets cover the whole stream, omitted is 0 and the two
retained halves are contiguous — the split is artificial and a multibyte
codepoint can straddle it. finish() now decodes the contiguous buffer as one
in that case; the per-side UTF-8 boundary trims and separate decoding apply
only when a real middle gap exists. Without this, a headTail retainer could
drop a character while reporting truncated:false.
2026-07-07 09:46:15 +08:00
Dudu-0223
7a1c2779a7 feat(retention): add dsh-retention bounded-output library
Ship @deepseek-ai/dsh-retention under packages/util/: pure ItemRetainer /
TextRetainer plus neutral notice helpers, so tools that cap model-facing
output share one "what did we keep, what did we omit, may we stop reading"
mechanic while keeping grouping, exit codes, provider errors, and recovery
prose tool-owned. The two retainers are separate names because they differ in
resource model: item-head can stop the upstream on the first over-cap probe
(shouldStop), while text tail/head-tail must read to the end. The library
documents glob/grep/bash/web_fetch/web_search mappings but migrates no tool
yet — glob/grep don't exist, and migration is deliberately separate work.

Flips the RFC to implemented/ and rewrites its skeleton to shipped reality.
2026-07-07 09:33:59 +08:00
Dudu-0223
760bc9aa6a fix: scope timeoutOf by deadline code so nesting composes (codex round 2)
Round 2 P2: timeoutOf() accepted ANY TimeoutReason, so under nesting — when
the upstream handed to deadline() is itself a deadline (the RFC's named
tools/execute middleware follow-up) and its outer timer fires first —
AbortSignal.any preserves the outer reason and the inner bash/web would report
the outer timeout as their own (timedOut / WEB_FETCH_TIMEOUT) though their local
timer never expired. Add an optional code to timeoutOf; bash and web pass their
own code, so a foreign timeout falls through to the upstream-cancel path.
2026-07-06 17:07:11 +08:00
Dudu-0223
b4ba84a1a9 fix: drop trailing blank line in fs README (codex review round 1)
Round 1 P3: the "No timeouts on file IO" section left the file ending in a
blank line, which the trailing-newline whitespace gate rejects. Declined P2
(late abort after a timeout is lost): that is the RFC's decided trade-off —
mutually-exclusive first-abort classification — and re-latching aborted would
violate the acceptance criterion.
2026-07-06 16:46:14 +08:00
Dudu-0223
8615e019d3 feat(timeout): add dsh-timeout and converge bash + web_fetch onto it
Timeout timing/classification was re-implemented three ways across the
tool-bearing capabilities, with the fusion of timeout+cancel and the
timeout-vs-cancel reason recovery being the error-prone parts. Extract that
shared half into a zero-dependency @deepseek-ai/dsh-timeout library
(clampTimeout/deadline/timeoutOf/TimeoutReason) and leave the non-shareable
hard-kill in each capability, per the timeout-library RFC.

bash: run() owns the deadline; runBash drops its killTimer and no longer
classifies (SpawnSpec/SpawnOutcome lose timeoutMs/timedOut/aborted), so the
public timedOut/aborted booleans become mutually-exclusive first-abort
classifications. web_fetch: the hand-rolled controller/timer/listener/
signal.reason dance is replaced by provider-owned deadline/timeoutOf, keeping
the WEB_FETCH_TIMEOUT / WEB_ABORTED contract. fs stays timeout-free (README
states why).
2026-07-06 16:25:04 +08:00
Tianyi Cui
7cdf8676dd Merge branch 'master' into codex/move-acp-snapshot-replay-doc 2026-07-06 00:56:13 +08:00
Tianyi Cui
5caceee380 Move ACP snapshot replay doc into package 2026-07-06 00:52:06 +08:00
Tianyi Cui
3633cf90c8 fix(review): contain provider-removed listener failures; pin the model-via-request path
ds-review-bot round 2, both warnings:

- subagent/provider-removed now routes through emitLifecycle (per-listener
  containment, the subagent/start|end precedent) instead of raw ctx.emit,
  whose dispatch halts on the first throw: a throwing subscriber can no
  longer starve a later mirror into keeping a stale tool, nor disrupt the
  backend fiber's teardown mid-disposer. provider-added deliberately keeps
  propagation (register-time rollback semantics, like the system-prompt
  registries); the asymmetry is documented on emitLifecycle, the event
  JSDoc, and the provider-lifecycle RFC.

- The documented model-via-agent/request fallback composes with a
  {{model}} persona via the ownership rule itself: the plugin supplying
  the model late states it early on the system-prompt/assemble waterfall.
  Declined re-ordering render after agent/request — it would break the
  agent/pre-step contract (compaction must measure the prompt the model
  sees). New loop test pins the supply path end-to-end; the RFC's
  {{model}} consequence bullet now covers supply as well as switch.
2026-07-06 00:34:47 +08:00
Tianyi Cui
bffdee88e4 Merge origin/master: adopt the uniform RFC format
This branch's two RFCs (prompt-variables, provider-lifecycle events)
conform to the new contract in the same merge: 'Status: implemented'
dateless header, 'Rejected alternatives' -> 'Alternatives considered',
'What we give up' folded into '## Consequences' (per the uniform-format
RFC's own rejection of a costs-only closer), and the index regenerated
into the new INDEX.md. docs/rfc/README.md resolved to master's rewrite.
2026-07-06 00:13:09 +08:00
Tianyi Cui
3f83a4ee96 review: the persona becomes the system-prompt plugin's deployment config
Review round 2 (tianyicui inline comments):

- dsh-system-prompt itself registers the harness:identity (-100) and
  deployment:persona (0) sections — they must survive a swapped loop
  plugin, so they leave dsh-agent-loop; the persona text is the plugin's
  own validated 'persona' config. The model/cwd variables STAY on the
  loop: runtime facts of the agents it drives.
- AgentOptions.systemPrompt is deleted with all its forwarding plumbing:
  the app configs' systemPrompt keys become 'persona' routed through
  dsh-agent-core (schema = z.intersect of the owners'), the ACP bridge
  and tool-subagent stop carrying persona configuration, and subagent
  children now render the deployment persona like every other agent.
- Example personas drop transport/interface trivia (ACP, CLI) — facts
  irrelevant to the model.
- Root CONTEXT.md removed (not idiomatic); its persona definition was
  wrong under the new ownership anyway.
- Docs, READMEs, the prompt-variables RFC, and generated catalogs
  updated; new loop test pins the assemble-waterfall escape valve
  (an emptied assembly sends NO system field).
2026-07-05 23:23:46 +08:00
Tianyi Cui
e6fad266a6 docs(rfc): define and enforce a uniform RFC format; adopt it across the corpus
Define the in-file RFC contract in docs/rfc/README.md § The file format:
the header block (`# RFC: <title>` plus a dateless Status enum
cross-checked against the lifecycle folder), the per-lifecycle body
skeleton (a Problem opener everywhere; Proposal/Alternatives considered/
Acceptance criteria/Risks in proposed/; present-tense Decision/
Consequences with proposal-era headings banned in implemented/; the
frozen proposal shape in rejected/), and a mandatory Alternatives
considered section with a date-fenced grandfather comment for pre-format
RFCs whose alternatives are not reconstructible from the record.

Enforce it with a new doc-sync gate, scripts/verify-rfc-format.ts, and
normalize all 112 RFCs to it: ~15 Status-line spellings collapse to the
enum, 29 Context openers become Problem, the 39 legacy-format XXX debt
markers are resolved and banned from reappearing, proposal-era sections
in implemented RFCs are rewritten to shipped reality (including the
web/fs/subagent seam RFCs' migration plans and test checklists, closing
the doc-tiers deferred-work item on the web seam), every RFC gains an
Alternatives considered section or the grandfather comment, and the
bilingual pair is re-mirrored and re-recorded.

Move the generated index tables out of README.md into a fully generated
docs/rfc/INDEX.md — gen-rfc-index now writes the whole file, and
verify-rfc-classification checks its freshness and rejects index-shaped
rows in the curated README — which makes room for the format contract to
live in the README front door instead of a separate FORMAT.md.

The decision record, and the first RFC written in the new format, is
docs/rfc/implemented/process/2026-07-05-uniform-rfc-format.md.
2026-07-05 22:58:25 +08:00
Tianyi Cui
2304f7a245 Merge origin/master: adopt the DeepSeek Harness SDK product name
Reconciliations beyond textual conflicts:
- product rename (DeepSeek Code -> DeepSeek Harness SDK) applied to the
  PR-added assertion in system-prompt.spec.ts that master's rename
  commit could not reach
- architecture.md: master's rewrite kept; this PR's prompt-assembly
  semantics re-added in the new doc's voice (Turn Flow footnote +
  service-spine row), within the 1630-word ceiling
- cordis catalog regenerated into master's split events.md/services.md
  (events-and-services.md deleted); module graph and doc graphs
  regenerated to pick up this PR's new events and dependency edges
2026-07-05 20:54:48 +08:00
Tianyi Cui
fdd65269f9 docs: synthesize architecture rewrite 2026-07-05 18:51:52 +08:00
Tianyi Cui
dca7a436e4 docs: update product name 2026-07-05 15:31:50 +08:00
Tianyi Cui
00cf8b693a feat(agent-loop): open every prompt with the harness identity section
A static harness:identity section at order -100 — the first occupant of
the documented negative band — states that the agent is powered by the
DeepSeek Harness SDK before the deployment's persona renders. Harness
attribution is a harness fact: it lives on the loop plugin, not in each
deployment's persona, so every agent (subagents included) carries it and
no YAML can forget it. A deployment that must drop it can remove the
section in the system-prompt/assemble waterfall.

Order-band docs updated in all five homes (PromptSection JSDoc, the
system-prompt and agent-loop READMEs, architecture.md, the RFC).
2026-07-05 11:37:32 +08:00
Tianyi Cui
2f191cc72b docs(agent): state the persona's template contract in its JSDoc
The AgentOptions.systemPrompt doc said the persona 'may reference'
variables without saying that every complete {{...}} group IS interpreted,
strictly, and that literal {{...}} prose has no escape syntax yet (the
RFC's recorded deferral). A persona author reads this line first; it now
carries the contract. (ds-review-bot inline finding: the escape mechanism
itself stays deferred per the RFC — pre-release, no external consumers,
and the failure is loud with the fix in the message.)
2026-07-05 10:48:40 +08:00
Tianyi Cui
e890a3373e test(review): pin the tool-subagent plugin fiber's lifecycle ownership; doc nits
Codex round-2 findings:

- A committed test now proves the REAL plugin fiber (not a direct apply)
  owns the provider-lifecycle listeners: disposing a mounted tool's fiber
  unmounts the tool and leaves the provider intact, and a fiber disposed
  while WAITING never zombie-mounts when its provider arrives later.
- TODO(subagent-dup-toolname) records the invalid-config blast radius of
  two waiting fibers sharing a toolName (the duplicate throw propagates
  through subagent/provider-added and rolls back the provider).
- CONTEXT.md drops its creation-history sentence; the RFC's acceptance
  checklist becomes present-tense shipped invariants (docs/AGENTS.md
  writing rules).
2026-07-05 03:40:22 +08:00
Tianyi Cui
e85e21c8b0 fix(review): close interpolation strictness holes; make tool-subagent mirror provider lifecycle
Codex round-1 findings, both confirmed:

- renderPrompt: variable lookup now uses Object.hasOwn (an unregistered
  {{constructor}} previously resolved through Object.prototype and spliced
  function source into the prompt), and a {{ that opens no complete group
  while a }} still follows ({{{model}}}, {{a{b}}) now throws instead of
  passing or partially interpolating. A lone {{ with no }} after it stays
  verbatim; substituted values are never re-scanned.
- tool-subagent: the apply-time provider lookup assumed a load order the
  cordis Loader does not guarantee (siblings start concurrently). The seam
  now announces subagent/provider-added/-removed and the tool mirrors the
  provider's lifecycle: registers when the provider is (or becomes)
  available, unregisters when it goes away, re-derives wording on reload.
  No load-order requirement remains.
- loop.spec containment test now proves live continuation: after the
  contained render failure, a waterfall listener rescues {{cwd}} and the
  same agent completes a real model turn.

RFC/READMEs updated to the shipped contract; cordis catalog regenerated.
2026-07-05 02:42:48 +08:00
Tianyi Cui
8e70efcfcc Merge remote-tracking branch 'origin/master' into codex/docs-graph-brainstorm
# Conflicts:
#	docs/architecture.md
#	docs/development.i18n.yaml
#	docs/development.md
#	docs/development.zh.md
2026-07-05 02:16:26 +08:00
Tianyi Cui
f256f3961d feat(system-prompt): prompt variables, persona-as-section, tool-guidance ownership
One principle: every fact in the assembled prompt has exactly one owner.

- dsh-system-prompt: merge-extensible AssembleContext on assemble();
  a variable(name, provider) registry; {{name}} interpolation in
  renderPrompt, strict (unknown/valueless/malformed references throw);
  duplicate section and variable names rejected; assembly carries
  resolved section text + variables through the assemble waterfall.
- dsh-agent declares AssembleContext.agent; dsh-agent-loop registers
  the agent:persona section (order 0 - identity renders before tool
  guidance) and the model/cwd variables, and drops its string join:
  renderPrompt(assembly) IS the full prompt.
- Tool guidance moves to its owners: descriptions carry per-tool
  semantics; sections only cross-call habits (tool:bash exit-code
  habit at order 105; read's not-shell nudge). todo/subagent need no
  section - their descriptions already carry the contract.
- SubagentProvider.inheritsParentContext (spawn/acp false, fork true);
  dsh-tool-subagent derives truthful per-provider wording and resolves
  the provider at load (backend must be listed first).
- Example personas shrink to identity + behavior with {{model}} (and
  {{cwd}} in the ACP tree); the welcome banner stops enumerating tools.

RFC: docs/rfc/implemented/architecture/2026-07-05-prompt-variables-and-tool-guidance-ownership.md
2026-07-05 01:54:46 +08:00
Tianyi Cui
5d97374a59 Merge remote-tracking branch 'origin/master' into codex/docs-graph-brainstorm
# Conflicts:
#	docs/module-graph.md
#	package.json
#	packages/core/tools/tests/gen-tool-catalog.spec.ts
2026-07-05 01:53:13 +08:00
Tianyi Cui
1316022cc8 docs: revise graph docs from review 2026-07-05 01:25:58 +08:00
Tianyi Cui
816a9f15f6 Merge remote-tracking branch 'origin/master' into split-cordis-catalog
# Conflicts:
#	docs/cordis-catalog/events.md
2026-07-05 01:22:05 +08:00
Tianyi Cui
e06807ce0e Merge remote-tracking branch 'origin/master' into codex/app-attribution-rfc 2026-07-05 01:00:40 +08:00
Tianyi Cui
b9e2d7ed85 Merge master into app attribution RFC 2026-07-05 00:45:39 +08:00
Tianyi Cui
25e02ae9a8 Split the cordis catalog into separate events and services documents
gen-cordis-catalog.ts now emits docs/cordis-catalog/events.md and
docs/cordis-catalog/services.md instead of the combined
events-and-services.md: a reader is either finding what to listen to or
what to call, and each axis now scans and deep-links as its own page.
Headings promote one level (scopes and ctx.<key> entries become H2), the
dispatch-mode legend lives on the events page, and the inherited tier
splits accordingly. --check verifies both files and names whichever is
stale.

Every reference updated in the same change (no compat redirects,
pre-release stance): architecture.md, AGENTS.md, docs/AGENTS.md tier row,
filesystem/subagent core-data-structures pages (the ctx.fs anchor
survives — slugs are heading-level-independent), fs README, four RFCs,
the tool-catalog and persistence-catalog generator intros (both
regenerated), and the bilingual development.md pair (re-recorded).
2026-07-05 00:45:06 +08:00
Tianyi Cui
a52c27ee07 Merge remote-tracking branch 'origin/master' into persistence-log-catalog
# Conflicts:
#	packages/hooks/hook-protocol/README.md
#	packages/hooks/hook-protocol/src/types.ts
2026-07-05 00:06:47 +08:00
Tianyi Cui
021dae58e2 Merge remote-tracking branch 'origin/master' into simpl-e-acp-trim
# Conflicts:
#	docs/rfc/README.md
2026-07-04 23:43:20 +08:00
Tianyi Cui
a23982b30a Regenerate catalog for post-merge line shifts; drop pruned continuation trigger from turn/start JSDoc
Merging master brought the producerless-vocabulary prune that removed
TurnTriggerMap's continuation variant: the source pointers above
SessionEventMap shifted by one line (the staleness CI caught on the
merge tree), and the new turn/start JSDoc still named the pruned
variant.
2026-07-04 23:42:47 +08:00
Tianyi Cui
2c9e5ee756 Merge remote-tracking branch 'origin/master' into persistence-log-catalog 2026-07-04 23:41:57 +08:00
Tianyi Cui
9857bb0c07 Merge remote-tracking branch 'origin/master' into simpl-g-hook-contract
# Conflicts:
#	docs/rfc/README.md
2026-07-04 23:40:05 +08:00
Tianyi Cui
f90f1c358e Merge remote-tracking branch 'origin/master' into simpl-e-acp-trim
# Conflicts:
#	docs/rfc/README.md
2026-07-04 23:22:16 +08:00
Tianyi Cui
c01d8c0dfc Merge remote-tracking branch 'origin/master' into simpl-g-hook-contract
# Conflicts:
#	docs/rfc/README.md
2026-07-04 23:22:08 +08:00
Tianyi Cui
54fc3d65ab Merge remote-tracking branch 'origin/master' into simpl-d-fs-write-only
# Conflicts:
#	docs/rfc/README.md
2026-07-04 23:21:46 +08:00
Tianyi Cui
2728e3bd5b Close remaining keyof-join holes the review verification found
- The owning top-level interface SessionEventMap must now be the SINGLE
  EXPORTED declaration in @deepseek-ai/dsh-session: a non-exported local
  interface (even inside the owning package) and a second exported copy
  are hard errors, so a same-named helper can no longer be catalogued as
  the on-disk vocabulary.
- Any SessionEventMap declaration carrying an extends clause is a hard
  error: inherited keys join keyof SessionEventMap but have no catalog
  row, so heritage is a silent-skip path the gate must reject.

Three new spec cases; RFC and module doc updated to match.
2026-07-04 23:21:23 +08:00
Tianyi Cui
c64cfbbe5d Merge remote-tracking branch 'origin/master' into simpl-a3-knobs
# Conflicts:
#	docs/rfc/README.md
2026-07-04 23:17:51 +08:00
Tianyi Cui
53dc3c8a28 Harden generator against Codex review findings
- A SessionEventMap member that is not a property signature with an
  explicit payload type is now a hard error instead of silently skipped —
  a method-form or type-less member joins keyof SessionEventMap and must
  not escape the catalog.
- A top-level interface SessionEventMap outside @deepseek-ai/dsh-session
  (ownership read from the package manifest) is now a hard error — an
  unrelated same-named local interface was previously catalogued as the
  on-disk vocabulary.
- JSDoc tag detection runs on the trimmed line, so an extra-indented
  '*  @mode' can no longer bypass the forbidden-tag check and leak into
  prose.

Four new spec cases cover these; RFC and module doc updated to describe
the enforced (not just assumed) invariants.
2026-07-04 23:11:42 +08:00
Tianyi Cui
9862eae898 Merge remote-tracking branch 'origin/master' into simpl-a2-vocab
# Conflicts:
#	docs/rfc/README.md
2026-07-04 23:11:07 +08:00
Tianyi Cui
232f314c3a Add generated persistence log event catalog with freshness + completeness gates
docs/persistence-catalog/log-events.md enumerates every SessionEventMap
member — the owning dsh-session vocabulary plus the dsh-compact and
dsh-hook-protocol declaration merges — with payload, surface/log-only badge,
JSDoc prose, and declaration site. scripts/gen-persistence-catalog.ts is a
pure AST pass in the gen-cordis-catalog mold: verify-persistence-catalog
(--check) joins doc-sync, so a stale committed catalog fails pre-push and CI.

The walk enforces JSDoc completeness (every member needs description prose;
@mode is rejected as a category error — log events do not dispatch on the
cordis bus), derives the surface badge from the SurfaceEventType union with a
stale-member cross-check, and hard-errors on duplicate declarations. Payloads
render through the TypeScript printer so newline-separated multi-line type
literals still emit valid one-line fragments.

Documented the five previously JSDoc-less core events (turn/step boundaries,
tool/call), removed the two stray @mode tags on the hook/* merges, and
replaced the hand-restated event enumerations (session.md hook/* table,
compact README table, hook-protocol README bullets, session README name-list
— whose merge note had already drifted) with links to the catalog. RFC:
docs/rfc/implemented/process/2026-07-04-persistence-log-catalog.md.
2026-07-04 22:58:28 +08:00
Tianyi Cui
8f62086feb Merge remote-tracking branch 'origin/master' into simpl-b2-app-boot
# Conflicts:
#	docs/rfc/README.md
2026-07-04 22:58:26 +08:00
Tianyi Cui
5f295de096 Merge remote-tracking branch 'origin/master' into simpl-f-steering-mirror
# Conflicts:
#	docs/rfc/README.md
2026-07-04 22:50:47 +08:00
Tianyi Cui
544cc0439e Merge remote app attribution branch
Resolve the RFC and implementation to defer OpenRouter-specific attribution headers and keep mandatory attribution to User-Agent only.
2026-07-04 22:47:53 +08:00
Tianyi Cui
1ddd6a9eea Merge remote-tracking branch 'origin/master' into simpl-a1-drop-image
# Conflicts:
#	docs/rfc/README.md
2026-07-04 22:46:55 +08:00