Merge remote-tracking branch 'origin/master' into xtr/react-loop-simplification

# Conflicts:
#	apps/web/tests/markdown-images.e2e.ts
#	docs/cordis-catalog/services.md
#	docs/core-data-structures/session.i18n.yaml
#	docs/event-producer-consumer.md
#	examples/acp-agent/tests/snapshots/cordis-inspect-jsdoc/session.jsonl
#	packages/compact/compact-basic/tests/compact-basic.spec.ts
#	packages/context/time-context/tests/invariant.spec.ts
#	packages/context/time-context/tests/time-context.spec.ts
#	packages/core/agent-loop/src/invariant.ts
#	packages/core/agent-loop/tests/contract-regressions.spec.ts
#	packages/core/agent-loop/tests/request-reconstruction.spec.ts
#	packages/core/agent/tests/agent.spec.ts
#	packages/core/session/README.i18n.yaml
#	packages/core/session/src/index.ts
#	packages/core/session/tests/derived-cache.spec.ts
#	packages/core/session/tests/request-header.spec.ts
#	packages/core/session/tests/session.spec.ts
#	packages/core/session/tests/surface.spec.ts
#	packages/fs/tool-str-replace-editor/tests/tools.spec.ts
#	packages/goal/goal/tests/goal.spec.ts
#	packages/plan/plan-mode/tests/plan-mode.spec.ts
#	packages/pty/pty-local/tests/index.spec.ts
#	packages/pty/pty-local/tests/local.spec.ts
#	packages/pty/pty/tests/service.spec.ts
#	packages/pty/tool-bash-persistent/tests/loader-composition.spec.ts
#	packages/pty/tool-bash-persistent/tests/tools.spec.ts
#	packages/pty/tool-pty/tests/loader-composition.spec.ts
#	packages/pty/tool-pty/tests/tools.spec.ts
#	packages/session-title/session-title-all-messages-llm/tests/provider.spec.ts
#	packages/tasks/tasks-local/tests/tasks.spec.ts
#	packages/ui/user-approval/tests/approval.spec.ts
This commit is contained in:
_Kerman
2026-08-05 13:53:35 +08:00
75 changed files with 1087 additions and 358 deletions

View File

@@ -54,7 +54,7 @@ function stubAgentForSession(session: Session): StubAgent {
/** Build a registry-compatible agent around a fresh session. */
function stubAgent(rawId: string, seed?: readonly import('@deepseek-ai/dsh-session').SessionEvent[]): StubAgent {
return stubAgentForSession(new Session(SessionId(rawId), seed))
return stubAgentForSession(Session.create(SessionId(rawId), seed))
}
async function harness(config: { defaultMaxGoalRounds?: number } = {}) {
@@ -235,7 +235,7 @@ describe('GoalService creation and replay', () => {
const { ctx, agent } = await harness()
// A same-id agent backed by a different session object — the live-instance
// check must reject it even though the ids match.
const impostor = stubAgentForSession(new Session(agent.id)).agent
const impostor = stubAgentForSession(Session.create(agent.id)).agent
expect(() => ctx.goals.get(impostor)).toThrow(expect.objectContaining({ code: 'GOAL_AGENT_NOT_LIVE' }))
expect(() => ctx.goals.create(impostor, { objective: 'no' })).toThrow(expect.objectContaining({
code: 'GOAL_AGENT_NOT_LIVE',
@@ -516,7 +516,7 @@ describe('goal replay validation', () => {
}
function oneChange(change: GoalChangeMeta) {
const session = new Session(SessionId(`validation-${Math.random()}`))
const session = Session.create(SessionId(`validation-${Math.random()}`))
appendChange(session, change)
return session.events
}
@@ -547,7 +547,7 @@ describe('goal replay validation', () => {
it('keeps durable goal state independent from inbox changes', () => {
const change = snapshotChange()
const session = new Session(SessionId('inbox-independent-change'))
const session = Session.create(SessionId('inbox-independent-change'))
appendChange(session, change)
expect(foldGoal(session.events)).toMatchObject({ goal: { id: change.goal.id, revision: 1 } })
const message = createUserMessage({
@@ -561,7 +561,7 @@ describe('goal replay validation', () => {
})
function foldPair(first: GoalSnapshotChangeMeta, second: GoalChangeMeta): ReturnType<typeof foldGoal> {
const session = new Session(SessionId(`validation-pair-${Math.random()}`))
const session = Session.create(SessionId(`validation-pair-${Math.random()}`))
appendChange(session, first)
appendChange(session, second)
return foldGoal(session.events)
@@ -570,7 +570,7 @@ describe('goal replay validation', () => {
it('ignores unrelated metadata and non-goal round sources', () => {
expect(decodeGoalChange(undefined)).toBeUndefined()
expect(decodeGoalChange({ kind: 'other' })).toBeUndefined()
const session = new Session(SessionId('unrelated'))
const session = Session.create(SessionId('unrelated'))
appendInjection(session, createUserMessage({
content: [{ type: 'text', text: 'other' }],
source: { kind: 'plugin', plugin: 'test' },
@@ -588,7 +588,7 @@ describe('goal replay validation', () => {
it('rejects rounds attributed to another goal', () => {
const change = snapshotChange()
const session = new Session(SessionId('other-goal-round'), oneChange(change))
const session = Session.create(SessionId('other-goal-round'), oneChange(change))
appendRound(session, { id: GoalId('goal-other'), revision: 1 }, 1)
expect(() => foldGoal(session.events)).toThrow('not the next admitted round')
})
@@ -660,7 +660,7 @@ describe('goal replay validation', () => {
roundsStarted: 2,
goal: { ...paused.goal, revision: 3, phase: 'active', maxGoalRounds: 2 },
})
const session = new Session(SessionId('exhausted-resume'))
const session = Session.create(SessionId('exhausted-resume'))
appendChange(session, base)
appendRound(session, base.goal, 1)
appendRound(session, base.goal, 2)
@@ -686,7 +686,7 @@ describe('goal replay validation', () => {
createdAt: 20,
updatedAt: 20,
})
const completedSession = new Session(SessionId('reuse-complete'))
const completedSession = Session.create(SessionId('reuse-complete'))
appendChange(completedSession, base)
appendChange(completedSession, complete)
appendChange(completedSession, sameCurrentId)
@@ -698,7 +698,7 @@ describe('goal replay validation', () => {
updatedAt: 20,
})
const secondComplete = mutation(second, 'complete', 'complete')
const nonAdjacentReuse = new Session(SessionId('reuse-non-adjacent'))
const nonAdjacentReuse = Session.create(SessionId('reuse-non-adjacent'))
appendChange(nonAdjacentReuse, base)
appendChange(nonAdjacentReuse, complete)
appendChange(nonAdjacentReuse, second)
@@ -709,7 +709,7 @@ describe('goal replay validation', () => {
const clear: GoalChangeMeta = {
kind: 'goal/change', version: 1, operation: 'clear', cleared: { id: base.goal.id, revision: 2 }, clearedAt: 11,
}
const clearedSession = new Session(SessionId('reuse-clear'))
const clearedSession = Session.create(SessionId('reuse-clear'))
appendChange(clearedSession, base)
appendChange(clearedSession, clear)
appendChange(clearedSession, sameCurrentId)
@@ -717,7 +717,7 @@ describe('goal replay validation', () => {
})
it('rejects non-positive goal round sources', () => {
const session = new Session(SessionId('goal-source-without-meta'))
const session = Session.create(SessionId('goal-source-without-meta'))
const source = { kind: 'goal', goalId: GoalId('goal-missing-meta'), revision: 1, round: 0 } as const
const turn = nextTurn(session)
session.append('turn/start', { turn })
@@ -763,7 +763,7 @@ describe('goal replay validation', () => {
it('folds a clear tombstone after a snapshot', () => {
const change = snapshotChange()
const session = new Session(SessionId('fold-clear'), oneChange(change))
const session = Session.create(SessionId('fold-clear'), oneChange(change))
const clear: GoalChangeMeta = {
kind: 'goal/change',
version: 1,

View File

@@ -23,7 +23,7 @@ interface StubAgent {
/** Build one registry-compatible live agent whose injections enter the durable inbox. */
function stubAgent(rawId: string, supplied?: Session): StubAgent {
const session = supplied ?? new Session(SessionId(rawId))
const session = supplied ?? Session.create(SessionId(rawId))
let status: AgentStatus = 'running'
const agent: Agent = {
id: session.id,
@@ -258,7 +258,7 @@ describe('goal tool execution authority', () => {
const created = ctx.goals.create(root.agent, { objective: 'resume the fork' })
closeTurn(root, originalTurn)
const forkId = SessionId('goal-tool-resumed-fork')
const forkSession = new Session(forkId, root.session.events, {
const forkSession = Session.create(forkId, root.session.events, {
version: SESSION_FORMAT_VERSION,
id: forkId,
createdAt: Date.now(),