Merge branch 'claude/web-llm-pi-ai-config-385e24' into claude/pi-ai-model-discovery

# Conflicts:
#	docs/cordis-catalog/events.md
#	docs/core-data-structures/core.i18n.yaml
#	docs/event-producer-consumer.md
#	packages/host/apiproxy/README.i18n.yaml
#	packages/llm/llm/README.i18n.yaml
This commit is contained in:
Yichen Jiang
2026-08-06 10:50:20 +08:00
822 changed files with 18341 additions and 14753 deletions

View File

@@ -0,0 +1,108 @@
'use strict';
/**
* Istanbul coverage reporter printing one clickable `path:line:col` record per
* uncovered statement, branch path, and function. Vitest's per-file threshold
* failures name only the file; this reporter supplies the exact locations,
* printed just above those ERROR lines (reports run before threshold checks).
* Files at 100% print nothing, so a green run stays silent.
*
* CommonJS by requirement: istanbul-reports loads custom reporters with a bare
* require() outside the tsx/ESM pipeline (istanbul-reports index.js create()),
* so this file can be neither TypeScript nor ESM. Wired into vitest.config.ts
* by absolute path — require() would resolve a relative specifier against
* istanbul-reports' own directory.
*/
const path = require('node:path');
const { ReportBase } = require('istanbul-lib-report');
/**
* Editor-convention `line:column` of an istanbul location start (istanbul
* columns are 0-based; editors and terminal link handlers expect 1-based).
*/
function pos(loc) {
return `${loc.start.line}:${loc.start.column + 1}`;
}
/** Whether a location carries a usable 1-based start line. */
function usable(loc) {
return Boolean(loc && loc.start && Number.isFinite(loc.start.line) && loc.start.line >= 1);
}
/**
* ` (to line:col)` suffix when the range end adds information beyond the
* start. v8-remapped whole-line statements carry end.column = Infinity; those
* degrade to a line-only suffix, or to nothing on a single line.
*/
function endSuffix(loc) {
const end = loc.end;
if (!end || !Number.isFinite(end.line) || end.line < 1) return '';
if (!Number.isFinite(end.column)) {
return end.line === loc.start.line ? '' : ` (to ${end.line})`;
}
if (end.line === loc.start.line && end.column === loc.start.column) return '';
return ` (to ${end.line}:${end.column + 1})`;
}
class UncoveredLocationsReport extends ReportBase {
constructor(opts = {}) {
super(opts);
// Vitest passes the resolved config root alongside reporter options.
this.projectRoot = opts.projectRoot || process.cwd();
this.records = [];
}
onStart() {
this.records = [];
}
onDetail(node) {
const fc = node.getFileCoverage();
const rel = path.relative(this.projectRoot, fc.path).split(path.sep).join('/');
const items = [];
const add = (loc, text) => items.push({ line: loc.start.line, column: loc.start.column, text });
for (const id of Object.keys(fc.statementMap)) {
if (fc.s[id] !== 0) continue;
const loc = fc.statementMap[id];
if (!usable(loc)) continue;
add(loc, `${rel}:${pos(loc)} uncovered statement${endSuffix(loc)}`);
}
for (const id of Object.keys(fc.fnMap)) {
if (fc.f[id] !== 0) continue;
const fn = fc.fnMap[id];
const loc = usable(fn.decl) ? fn.decl : fn.loc;
if (!usable(loc)) continue;
const name = fn.name ? ` ${fn.name}` : '';
add(loc, `${rel}:${pos(loc)} uncovered function${name}`);
}
for (const id of Object.keys(fc.branchMap)) {
const counts = fc.b[id];
const branch = fc.branchMap[id];
for (let i = 0; i < counts.length; i += 1) {
if (counts[i] !== 0) continue;
// Implicit arms (e.g. a missing else) may carry an empty location;
// fall back to the branch's own span so the record stays clickable.
const loc = usable(branch.locations && branch.locations[i]) ? branch.locations[i] : branch.loc;
if (!usable(loc)) continue;
add(loc, `${rel}:${pos(loc)} uncovered branch (${branch.type}, path ${i + 1}/${counts.length})`);
}
}
if (items.length === 0) return;
items.sort((a, b) => a.line - b.line || a.column - b.column);
for (const item of items) this.records.push(item.text);
}
onEnd() {
if (this.records.length === 0) return;
console.log(`\nUncovered locations (per-file 100% gate): ${this.records.length}`);
for (const record of this.records) console.log(record);
console.log('');
}
}
module.exports = UncoveredLocationsReport;

View File

@@ -28,8 +28,6 @@ export const LINK_MAP: Readonly<Record<string, string>> = {
ContinuationDecision: 'core.md',
ContinuationStop: 'core.md',
GenerateOptions: 'core.md',
InboxItem: 'core.md',
InboxPlacement: 'core.md',
MessageId: 'core.md',
HookContext: 'core.md',
SettleReason: 'core.md',
@@ -49,9 +47,10 @@ export const LINK_MAP: Readonly<Record<string, string>> = {
Message: 'core.md',
MessageSource: 'core.md',
UserMessage: 'session.md',
PromptDecision: 'core.md',
RequestError: 'core.md',
PreStepDecision: 'core.md',
PreStepContext: 'core.md',
RequestErrorAction: 'core.md',
RequestFailureContext: 'core.md',
PreparedReferencedMessage: 'session-reference.md',
SessionReferenceCandidate: 'session-reference.md',
SessionReferenceInput: 'session-reference.md',

View File

@@ -1153,20 +1153,22 @@ function renderLifecycle(): string {
' participant Session',
' participant SDK as UI or SDK listener',
' User->>Agent: followup(content)',
` Agent-->>SDK: ${mermaidCode('agent/inbox/enqueue')}`,
` Agent-->>SDK: ${mermaidCode('agent/inbox/spliced')}`,
` Agent-->>SDK: ${mermaidCode('agent/inbox/inserted')} { message }`,
' Agent->>Driver: queued work wakes driver',
` Driver-->>SDK: ${mermaidCode('agent/status')} running`,
' Note over Agent,Driver: next-step acceptance window opens',
` Driver->>Hooks: ${mermaidCode('agent/prompt-submit')} waterfall`,
' Hooks-->>Driver: authoritative allow, block, or add context',
' alt prompt blocked or admission failed',
' Driver-->>Driver: append context-only batch or keep steering boundary pending',
' else prompt allowed',
' Note over Agent,Driver: claim pending next-step input plus one queued prompt',
` Driver-->>SDK: ${mermaidCode('agent/inbox/spliced')} pure deletion`,
` Driver-->>SDK: ${mermaidCode('agent/inbox/claimed')} { message, turn } per message`,
` Driver->>Hooks: ${mermaidCode('agent/pre-step')} waterfall`,
' Hooks-->>Driver: authoritative reject or enter(messages)',
' alt proposed step rejected or pre-step failed',
' Driver-->>Driver: claimed batch stays removed, no turn opens',
' else enter proposed step',
` Driver->>Session: ${mermaidCode('turn/start')}`,
` Driver->>Session: ${mermaidCode('user/message')}`,
` Driver->>Prompt: ${mermaidCode('system-prompt/assemble')} waterfall`,
` Driver-->>Driver: ${mermaidCode('agent/step')} serial checkpoint`,
` Driver->>Session: ${mermaidCode('step/start')}`,
` Driver->>Session: ${mermaidCode('user/message')} per entered message`,
` Driver->>Prompt: ${mermaidCode('system-prompt/assemble')} waterfall`,
` Driver->>LLM: ${mermaidCode('agent/request')} waterfall, then ${mermaidCode('llm/stream')} waterfall`,
' LLM-->>Driver: StreamChunk*',
` Driver->>Session: ${mermaidCode('assistant/chunk')}*`,
@@ -1189,11 +1191,17 @@ function renderLifecycle(): string {
` Driver->>Session: ${mermaidCode('tool/result')}`,
' end',
' end',
' Driver->>Session: post-tool context and steering (no prompt-submit)',
` Driver->>Session: ${mermaidCode('step/end')}`,
` Driver->>Hooks: ${mermaidCode('agent/turn-stopping')} serial terminal checkpoint`,
' opt natural stop and next-step inbox empty',
` Driver->>Hooks: ${mermaidCode('agent/turn-stopping')} serial terminal checkpoint`,
' end',
' opt next-step input is pending',
' Driver-->>Driver: claim pending next-step input',
` Driver-->>SDK: ${mermaidCode('agent/inbox/claimed')} { message, turn } per message`,
` Driver->>Hooks: ${mermaidCode('agent/pre-step')} waterfall`,
' Hooks-->>Driver: authoritative reject or enter(messages)',
' end',
' end',
' Note over Agent,Driver: next-step acceptance window closes',
` Driver->>Session: ${mermaidCode('turn/end')}`,
' end',
` Driver-->>SDK: ${mermaidCode('agent/status')} idle`,
@@ -1201,9 +1209,9 @@ function renderLifecycle(): string {
'',
'The `assistant/message` edge records every successful provider call, including content-less and `max-tokens` finishes. Empty content stays out of derived history while the durable anchor retains usage and exact chunk provenance, including an explicit empty source set.',
'',
'`dsh-compact-basic` uses `agent/step` for pressure before request derivation and `agent/request-error` only for canonical context overflow. Once either trigger qualifies, optional tool-result pruning runs before summary selection. Recovery works between the closed failed step and failed turn close, and opens a fresh retry turn only when pruning or summarization advances the surface replacement generation; otherwise the original request error remains authoritative.',
'`dsh-compact-basic` uses `agent/pre-step` for pressure before request derivation and `agent/request-error` only for canonical context overflow. Once either trigger qualifies, optional tool-result pruning runs before summary selection. Recovery works between the closed failed step and failed turn close, and opens a fresh retry turn only when pruning or summarization advances the surface replacement generation; otherwise the original request error remains authoritative.',
'',
'The returned `agent/prompt-submit` allow is authoritative; listeners wrapping `next()` preserve downstream content and additional contexts unless replacement is intentional. Steering bypasses that waterfall and joins at its durable checkpoint.',
'The returned `agent/pre-step` decision is authoritative; listeners wrapping `next()` preserve downstream messages unless replacement is intentional. Steering and injected context pass through the same waterfall after a later boundary claims their next-step batch.',
'',
'SDK users that need replayable transcript data should consume `session/event`; `agent/*` is the live coordination surface for queue/status, prompt interception, request shaping, steering, continuation, and errors.',
'',

View File

@@ -311,7 +311,7 @@ const TOOL_PACKAGES: ToolPackage[] = [
dir: 'tool-goal',
source: 'packages/goal/tool-goal/src/index.ts',
requires: ['ctx.tools', 'ctx.agents', 'ctx.goals', 'ctx.systemPrompt', 'a calling Agent in an authorized open turn'],
writes: ['tool/call', 'user/message goal snapshot for mutations', 'tool/result'],
writes: ['tool/call', 'goal/change for mutations', 'tool/result'],
async mount(ctx) {
await ctx.plugin(AgentRegistry)
await ctx.plugin(GoalService)

View File

@@ -14,6 +14,7 @@ import {
} from 'node:fs'
import { spawnSync } from 'node:child_process'
import { dirname, isAbsolute, join, resolve } from 'node:path'
import lefthookPackage from 'lefthook/package.json' with { type: 'json' }
const MINIMUM_GIT = [2, 26, 0]
const HOOKS_DIRECTORY = 'dsh-hooks'
@@ -596,6 +597,7 @@ function refuseScopedHooksPath(entry) {
async function main() {
if (process.env.CI === 'true' || process.env.GITHUB_ACTIONS === 'true') return
if (typeof lefthookPackage.bin?.lefthook !== 'string') return
const probe = spawnSync('git', ['rev-parse', '--show-toplevel'], { encoding: 'utf8' })
if (probe.status !== 0) return
const root = stripGitLineTerminator(probe.stdout)

View File

@@ -19,6 +19,9 @@ import { afterEach, describe, expect, it } from 'vitest'
const installer = fileURLToPath(new URL('./install-lefthook.mjs', import.meta.url))
const fixtures: string[] = []
// Multi-worktree cases spawn several Git and Node subprocesses; coverage concurrency can
// legitimately exceed Vitest's default deadline without changing the installer behavior.
const MULTI_PROCESS_TEST_TIMEOUT_MS = 20_000
interface Fixture {
container: string
@@ -260,7 +263,7 @@ describe('worktree-local Lefthook installer', { timeout: 15_000 }, () => {
git(fixture, fixture.main, ['worktree', 'remove', '--force', fixture.linked])
expect(readFileSync(join(mainHooks, 'pre-commit'), 'utf8')).toBe(mainHookBeforeRemoval)
expect(readFileSync(legacyHook, 'utf8')).toBe('#!/bin/sh\n# legacy hook\n')
})
}, MULTI_PROCESS_TEST_TIMEOUT_MS)
it('replaces the owned hook path Git copies into a newly added worktree', async () => {
const fixture = createFixture()
@@ -284,7 +287,7 @@ describe('worktree-local Lefthook installer', { timeout: 15_000 }, () => {
'# config=late-linked-worktree-config',
)
expect(readFileSync(join(mainHooks, 'pre-commit'), 'utf8')).toBe(mainHookBefore)
})
}, MULTI_PROCESS_TEST_TIMEOUT_MS)
it('serializes concurrent installs and keeps repeated output stable', async () => {
const fixture = createFixture()
@@ -305,7 +308,7 @@ describe('worktree-local Lefthook installer', { timeout: 15_000 }, () => {
expect(readFileSync(mainHookPath, 'utf8')).toBe(initialHook)
expect(existsSync(join(commonDirectory(fixture), 'dsh-lefthook-install.lock'))).toBe(false)
expect(existsSync(join(hooksPath(fixture, fixture.main), '.fake-lefthook-running'))).toBe(false)
})
}, MULTI_PROCESS_TEST_TIMEOUT_MS)
it('waits for a concurrent installer to finish publishing its lock record', async () => {
const fixture = createFixture()
@@ -343,7 +346,7 @@ describe('worktree-local Lefthook installer', { timeout: 15_000 }, () => {
expect(readFileSync(join(movedHooks, '.dsh-lefthook-owned'), 'utf8')).toContain(
JSON.stringify(movedHooks),
)
})
}, MULTI_PROCESS_TEST_TIMEOUT_MS)
it.skipIf(process.platform === 'win32')('refuses a multiply linked ownership marker before relocation rewrites it', async () => {
const fixture = createFixture()
@@ -384,7 +387,7 @@ describe('worktree-local Lefthook installer', { timeout: 15_000 }, () => {
expect(result.stderr).toContain('non-regular or multiply linked hook entry')
expect(readFileSync(externalHook, 'utf8')).toBe(externalContent)
}
})
}, MULTI_PROCESS_TEST_TIMEOUT_MS)
it('restores the marker-backed stale hook path when relocation reinstall fails', async () => {
const fixture = createFixture()

View File

@@ -108,33 +108,8 @@
},
{
"doc": "docs/core-data-structures/core.md",
"symbol": "SendTarget",
"source": "packages/core/agent/src/types.ts"
},
{
"doc": "docs/core-data-structures/core.md",
"symbol": "InboxPlacement",
"source": "packages/core/agent/src/types.ts"
},
{
"doc": "docs/core-data-structures/core.md",
"symbol": "InboxItem",
"source": "packages/core/agent/src/types.ts"
},
{
"doc": "docs/core-data-structures/core.md",
"symbol": "InboxAction",
"source": "packages/core/agent/src/types.ts"
},
{
"doc": "docs/core-data-structures/core.md",
"symbol": "InboxActionResult",
"source": "packages/core/agent/src/types.ts"
},
{
"doc": "docs/core-data-structures/core.md",
"symbol": "SendOptions",
"source": "packages/core/agent/src/types.ts"
"symbol": "InboxTarget",
"source": "packages/core/agent/src/inbox.ts"
},
{
"doc": "docs/core-data-structures/core.md",
@@ -144,7 +119,7 @@
{
"doc": "docs/core-data-structures/core.md",
"symbol": "AgentCancelCause",
"source": "packages/core/agent/src/types.ts"
"source": "packages/core/session/src/types.ts"
},
{
"doc": "docs/core-data-structures/core.md",
@@ -153,7 +128,12 @@
},
{
"doc": "docs/core-data-structures/core.md",
"symbol": "PromptDecision",
"symbol": "PreStepContext",
"source": "packages/core/agent/src/types.ts"
},
{
"doc": "docs/core-data-structures/core.md",
"symbol": "PreStepDecision",
"source": "packages/core/agent/src/types.ts"
},
{
@@ -161,11 +141,6 @@
"symbol": "RequestErrorAction",
"source": "packages/core/agent/src/types.ts"
},
{
"doc": "docs/core-data-structures/core.md",
"symbol": "RequestError",
"source": "packages/core/agent/src/types.ts"
},
{
"doc": "docs/core-data-structures/core.md",
"symbol": "SessionStartSource",
@@ -380,7 +355,7 @@
},
{
"doc": "docs/core-data-structures/session.md",
"symbol": "TurnTriggerMap",
"symbol": "TurnEndCancelCause",
"source": "packages/core/session/src/types.ts"
},
{