Merge latest origin/master into parallel-tool-call
# Conflicts: # docs/architecture.md # examples/acp-agent/tests/snapshots/bash-spill/session.jsonl # examples/acp-agent/tests/snapshots/escalation-approved/session.jsonl # examples/acp-agent/tests/snapshots/escalation-rejected/session.jsonl # examples/acp-agent/tests/snapshots/hook-cc-pretool-ask/session.jsonl # packages/core/agent-loop/src/loop.ts
This commit is contained in:
@@ -4,8 +4,8 @@
|
||||
"docs/architecture.md": 1790,
|
||||
"docs/cordis-primer.md": 600,
|
||||
"docs/defensive-patterns.md": 550,
|
||||
"docs/testing.md": 800,
|
||||
"examples/AGENTS.md": 200,
|
||||
"docs/testing.md": 960,
|
||||
"examples/AGENTS.md": 310,
|
||||
"packages/AGENTS.md": 290,
|
||||
"packages/README.md": 760
|
||||
}
|
||||
|
||||
@@ -87,6 +87,14 @@ const SERVICE_ROLES: ServiceRole[] = [
|
||||
consumers: ['agent-loop', 'compact-basic'],
|
||||
note: 'Adapters register provider implementations; the loop and compaction call the provider-neutral stream service.',
|
||||
},
|
||||
{
|
||||
key: 'tokenMeter',
|
||||
pkg: 'token-meter',
|
||||
title: 'Replay token measurement',
|
||||
mode: 'core',
|
||||
consumers: ['compact-basic'],
|
||||
note: 'Owns isolated per-session replay folds; pressure consumers share immutable revisioned measurements.',
|
||||
},
|
||||
{
|
||||
key: 'sessions',
|
||||
pkg: 'session',
|
||||
@@ -859,6 +867,8 @@ function renderLifecycle(): string {
|
||||
` Driver-->>SDK: ${mermaidCode('agent/status')} idle`,
|
||||
'```',
|
||||
'',
|
||||
'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.',
|
||||
'',
|
||||
'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.',
|
||||
'',
|
||||
...maintenanceFooter(maintenance),
|
||||
@@ -886,9 +896,9 @@ function renderToolPipeline(): string {
|
||||
` owned["Tool-owned session events<br/>${mermaidCode('todo/write')}, ${mermaidCode('fs/observed')}, ${mermaidCode('hook/invoked')}, ${mermaidCode('hook/result')}, ${mermaidCode('tool/code-dispatch')}"]`,
|
||||
` post["${mermaidCode('tools/post-execute')} waterfall<br/>accept, block, replace, add context"]`,
|
||||
` final["${mermaidCode('tools/result')} synchronous notification<br/>frozen authoritative outcome"]`,
|
||||
' context["Buffered additionalContexts<br/>context/message after all tool results"]',
|
||||
' context["Active-batch additionalContexts FIFO<br/>context/message after recorded tool results"]',
|
||||
` toolResult["Session event: ${mermaidCode('tool/result')}<br/>single model-facing outcome"]`,
|
||||
' allResults["All calls in the step settled<br/>and tool/result events recorded"]',
|
||||
' allResults["Tool batch settled<br/>recorded tool/result events complete"]',
|
||||
' presentResult["UI completed card<br/>presentResult(args, result)"]',
|
||||
' model --> toolCall',
|
||||
' toolCall --> presentCall',
|
||||
|
||||
@@ -133,7 +133,7 @@ const TOOL_PACKAGES: ToolPackage[] = [
|
||||
await ctx.plugin(ToolCordis)
|
||||
},
|
||||
note:
|
||||
'Ships in examples/cordis-agent only (a deliberate opt-in — mounted code gets the real ctx, see docs/rfc/implemented/feature/2026-07-08-self-referential-cordis-toolset.md). Plugins the model mounts may register ADDITIONAL model-visible tools at runtime; the request-header ToolsDelta logs those tool-set changes.',
|
||||
'Ships in examples/cordis-agent only (a deliberate opt-in — mounted code gets the real ctx, see docs/rfc/implemented/feature/2026-07-08-self-referential-cordis-toolset.md). Plugins the model mounts may register ADDITIONAL model-visible tools at runtime; a full changed request header logs those tool-set changes.',
|
||||
},
|
||||
{
|
||||
pkg: '@deepseek-ai/dsh-tool-fs',
|
||||
|
||||
@@ -163,11 +163,13 @@ function gatesForMode(selected: Mode): Gate[] {
|
||||
]
|
||||
case 'ci-coverage':
|
||||
return [
|
||||
pnpmScript('build', 'build'),
|
||||
coverageGate(),
|
||||
]
|
||||
case 'ci-snapshot':
|
||||
return [
|
||||
pnpmScript('snapshot', 'test:snapshot'),
|
||||
pnpmScript('build', 'build'),
|
||||
snapshotGate(),
|
||||
]
|
||||
case 'ci-artifacts':
|
||||
return ciArtifactGates()
|
||||
@@ -186,7 +188,7 @@ function gatesForMode(selected: Mode): Gate[] {
|
||||
pnpmScript('cordis-config', 'verify-cordis-config', { label: 'Cordis config' }),
|
||||
pnpmScript('test', 'test'),
|
||||
pnpmScript('duplication', 'duplication'),
|
||||
pnpmScript('snapshot', 'test:snapshot'),
|
||||
snapshotGate(),
|
||||
pnpmScript('build', 'build'),
|
||||
...hygieneLeafGates({ artifactNeeds: ['build'] }),
|
||||
...docSyncLeafGates({
|
||||
@@ -207,7 +209,7 @@ function ciPrimaryGates(): Gate[] {
|
||||
lintGate(),
|
||||
pnpmScript('duplication', 'duplication'),
|
||||
coverageGate(),
|
||||
pnpmScript('snapshot', 'test:snapshot'),
|
||||
snapshotGate(),
|
||||
demoSmokeGate({ needs: ['lint'] }),
|
||||
...docSyncLeafGates(),
|
||||
pnpmScript('module-graph', 'verify-module-graph', { label: 'module graph' }),
|
||||
@@ -281,6 +283,18 @@ function coverageGate(): Gate {
|
||||
...positiveIntArg('DSH_COVERAGE_MAX_WORKERS', '--maxWorkers'),
|
||||
], {
|
||||
label: 'test:coverage',
|
||||
env: { DSH_EXAMPLE_MODE: 'lib' },
|
||||
needs: ['build'],
|
||||
})
|
||||
}
|
||||
|
||||
// The snapshot suite boots the example bins in `lib` mode (built artifact under plain Node,
|
||||
// plugins via real exports) — CI and pre-push already build, so they exercise what ships rather
|
||||
// than the tsx/source path dev uses. It therefore waits on `build`.
|
||||
function snapshotGate(): Gate {
|
||||
return pnpmScript('snapshot', 'test:snapshot', {
|
||||
env: { DSH_EXAMPLE_MODE: 'lib' },
|
||||
needs: ['build'],
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -707,7 +707,7 @@ def normalize_snapshot_value(
|
||||
|
||||
|
||||
def scrub_snapshot_header(value: dict[object, object]) -> None:
|
||||
"""Tokenize request-header bulk while retaining delta tool names."""
|
||||
"""Tokenize full request-header bulk while retaining tool names."""
|
||||
data = value.get("data")
|
||||
if not isinstance(data, dict):
|
||||
return
|
||||
@@ -725,29 +725,6 @@ def scrub_snapshot_header(value: dict[object, object]) -> None:
|
||||
]
|
||||
if isinstance(header.get("messagePrefix"), list):
|
||||
header["messagePrefix"] = ["{{messagePrefix}}" for _ in header["messagePrefix"]]
|
||||
return
|
||||
if value.get("type") != "request/header-delta":
|
||||
return
|
||||
system = data.get("system")
|
||||
if isinstance(system, dict) and isinstance(system.get("insert"), list):
|
||||
system["insert"] = ["{{system}}" for _ in system["insert"]]
|
||||
tools = data.get("tools")
|
||||
if isinstance(tools, dict):
|
||||
for key in ("added", "changed"):
|
||||
if isinstance(tools.get(key), list):
|
||||
tools[key] = [scrub_snapshot_tool_schema(tool) for tool in tools[key]]
|
||||
if isinstance(data.get("messagePrefix"), list):
|
||||
data["messagePrefix"] = ["{{messagePrefix}}" for _ in data["messagePrefix"]]
|
||||
|
||||
|
||||
def scrub_snapshot_tool_schema(value: object) -> object:
|
||||
"""Keep a changed tool's name while tokenizing its schema bulk."""
|
||||
if not isinstance(value, dict):
|
||||
return value
|
||||
return {
|
||||
key: item if key == "name" else "{{tools}}"
|
||||
for key, item in value.items()
|
||||
}
|
||||
|
||||
|
||||
def render_jsonl(records: list[object]) -> str:
|
||||
|
||||
@@ -253,7 +253,7 @@
|
||||
"workflow"
|
||||
]
|
||||
},
|
||||
"reason": "fallback"
|
||||
"reason": "change"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -915,22 +915,29 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "request/header-delta",
|
||||
"type": "request/header",
|
||||
"seq": 56,
|
||||
"time": 0,
|
||||
"data": {
|
||||
"system": {
|
||||
"keepStart": 62,
|
||||
"keepEnd": 34,
|
||||
"insert": []
|
||||
"header": {
|
||||
"config": {
|
||||
"model": "smoke-model"
|
||||
},
|
||||
"system": "{{system}}",
|
||||
"tools": [
|
||||
"bash",
|
||||
"bash_kill",
|
||||
"bash_output",
|
||||
"cordis_inspect",
|
||||
"cordis_mount",
|
||||
"cordis_unmount",
|
||||
"run_code",
|
||||
"skill",
|
||||
"subagent",
|
||||
"workflow"
|
||||
]
|
||||
},
|
||||
"tools": {
|
||||
"added": [],
|
||||
"removed": [
|
||||
"snapshot_double"
|
||||
],
|
||||
"changed": []
|
||||
}
|
||||
"reason": "change"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -1396,7 +1403,7 @@
|
||||
"workflow"
|
||||
]
|
||||
},
|
||||
"reason": "fallback"
|
||||
"reason": "change"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2358,22 +2365,29 @@
|
||||
"payload": {
|
||||
"sessionId": "{{parent}}",
|
||||
"event": {
|
||||
"type": "request/header-delta",
|
||||
"type": "request/header",
|
||||
"seq": 56,
|
||||
"time": 0,
|
||||
"data": {
|
||||
"system": {
|
||||
"keepStart": 62,
|
||||
"keepEnd": 34,
|
||||
"insert": []
|
||||
"header": {
|
||||
"config": {
|
||||
"model": "smoke-model"
|
||||
},
|
||||
"system": "{{system}}",
|
||||
"tools": [
|
||||
"bash",
|
||||
"bash_kill",
|
||||
"bash_output",
|
||||
"cordis_inspect",
|
||||
"cordis_mount",
|
||||
"cordis_unmount",
|
||||
"run_code",
|
||||
"skill",
|
||||
"subagent",
|
||||
"workflow"
|
||||
]
|
||||
},
|
||||
"tools": {
|
||||
"added": [],
|
||||
"removed": [
|
||||
"snapshot_double"
|
||||
],
|
||||
"changed": []
|
||||
}
|
||||
"reason": "change"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
{"type":"tool/result","seq":11,"time":0,"data":{"turn":1,"step":1,"callId":"advanced-mount","content":[{"type":"text","text":"mounted dyn-1 (plugin \"<anonymous>\", state: active)"}],"isError":false},"sourceEventSeqs":[10],"surfaceOp":"append"}
|
||||
{"type":"step/end","seq":12,"time":0,"data":{"turn":1,"step":1}}
|
||||
{"type":"step/start","seq":13,"time":0,"data":{"turn":1,"step":2}}
|
||||
{"type":"request/header","seq":14,"time":0,"data":{"header":{"config":{"model":"smoke-model"},"system":"{{system}}","tools":["bash","bash_kill","bash_output","cordis_inspect","cordis_mount","cordis_unmount","run_code","skill","snapshot_double","subagent","workflow"]},"reason":"fallback"}}
|
||||
{"type":"request/header","seq":14,"time":0,"data":{"header":{"config":{"model":"smoke-model"},"system":"{{system}}","tools":["bash","bash_kill","bash_output","cordis_inspect","cordis_mount","cordis_unmount","run_code","skill","snapshot_double","subagent","workflow"]},"reason":"change"}}
|
||||
{"type":"assistant/chunk","seq":15,"time":0,"data":{"turn":1,"step":2,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}}}
|
||||
{"type":"assistant/chunk","seq":16,"time":0,"data":{"turn":1,"step":2,"chunk":{"type":"tool-call-delta","index":0,"id":"advanced-code","name":"run_code","argumentsDelta":"{\"code\": \"return await tools.snapshot_double({ value: 21 })\"}"}}}
|
||||
{"type":"assistant/chunk","seq":17,"time":0,"data":{"turn":1,"step":2,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"advanced-code","name":"run_code","arguments":"{\"code\": \"return await tools.snapshot_double({ value: 21 })\"}"}}}}
|
||||
@@ -55,7 +55,7 @@
|
||||
{"type":"tool/result","seq":53,"time":0,"data":{"turn":1,"step":5,"callId":"advanced-unmount","content":[{"type":"text","text":"unmounted dyn-1 (plugin \"<anonymous>\")"}],"isError":false},"sourceEventSeqs":[52],"surfaceOp":"append"}
|
||||
{"type":"step/end","seq":54,"time":0,"data":{"turn":1,"step":5}}
|
||||
{"type":"step/start","seq":55,"time":0,"data":{"turn":1,"step":6}}
|
||||
{"type":"request/header-delta","seq":56,"time":0,"data":{"system":{"keepStart":62,"keepEnd":34,"insert":[]},"tools":{"added":[],"removed":["snapshot_double"],"changed":[]}}}
|
||||
{"type":"request/header","seq":56,"time":0,"data":{"header":{"config":{"model":"smoke-model"},"system":"{{system}}","tools":["bash","bash_kill","bash_output","cordis_inspect","cordis_mount","cordis_unmount","run_code","skill","subagent","workflow"]},"reason":"change"}}
|
||||
{"type":"assistant/chunk","seq":57,"time":0,"data":{"turn":1,"step":6,"chunk":{"type":"block-start","index":0,"blockType":"text"}}}
|
||||
{"type":"assistant/chunk","seq":58,"time":0,"data":{"turn":1,"step":6,"chunk":{"type":"text-delta","index":0,"text":"ADVANCED_EXECUTABLE_OK"}}}
|
||||
{"type":"assistant/chunk","seq":59,"time":0,"data":{"turn":1,"step":6,"chunk":{"type":"block-end","index":0,"block":{"type":"text","text":"ADVANCED_EXECUTABLE_OK"}}}}
|
||||
|
||||
@@ -34,6 +34,9 @@
|
||||
{ "doc": "docs/core-data-structures/llm-streaming.md", "symbol": "ContentBlockMap", "source": "packages/llm/llm/src/types.ts" },
|
||||
{ "doc": "docs/core-data-structures/llm-streaming.md", "symbol": "AppIdentity", "source": "packages/llm/llm/src/attribution.ts" },
|
||||
|
||||
{ "doc": "docs/core-data-structures/token-meter.md", "symbol": "TokenMeasurement", "source": "packages/llm/token-meter/src/types.ts" },
|
||||
{ "doc": "docs/core-data-structures/token-meter.md", "symbol": "TokenSurfaceNode", "source": "packages/llm/token-meter/src/types.ts" },
|
||||
|
||||
{ "doc": "docs/core-data-structures/session.md", "symbol": "ContextEnvelope", "source": "packages/core/session/src/types.ts" },
|
||||
{ "doc": "docs/core-data-structures/session.md", "symbol": "SessionEventMap", "source": "packages/core/session/src/types.ts" },
|
||||
{ "doc": "docs/core-data-structures/session.md", "symbol": "EpochHeader", "source": "packages/core/session/src/types.ts" },
|
||||
@@ -44,7 +47,6 @@
|
||||
{ "doc": "docs/core-data-structures/session.md", "symbol": "SurfaceEventType", "source": "packages/core/session/src/types.ts" },
|
||||
{ "doc": "docs/core-data-structures/session.md", "symbol": "SurfaceOp", "source": "packages/core/session/src/types.ts" },
|
||||
{ "doc": "docs/core-data-structures/session.md", "symbol": "SurfaceIntent", "source": "packages/core/session/src/types.ts" },
|
||||
{ "doc": "docs/core-data-structures/session.md", "symbol": "SurfaceNode", "source": "packages/core/session/src/surface.ts" },
|
||||
{ "doc": "docs/core-data-structures/session.md", "symbol": "SurfaceFoldReplacement", "source": "packages/core/session/src/surface.ts" },
|
||||
{ "doc": "docs/core-data-structures/session.md", "symbol": "SurfaceFoldResult", "source": "packages/core/session/src/surface.ts" },
|
||||
|
||||
|
||||
@@ -1,18 +1,32 @@
|
||||
/**
|
||||
* Reject JavaScript expressions in Cordis Loader entry metadata.
|
||||
* Validate Cordis Loader entry metadata and example package resolution.
|
||||
*
|
||||
* The Loader interpolates only a plugin entry's `config`; expression objects in
|
||||
* fields such as `disabled` remain truthy data and silently change composition.
|
||||
* Example configs run from built packages, so every named package must resolve
|
||||
* from the examples workspace and every local package must be in the root
|
||||
* TypeScript project graph.
|
||||
*/
|
||||
|
||||
import { globSync, readFileSync } from 'node:fs'
|
||||
import { resolve } from 'node:path'
|
||||
import { dirname, relative, resolve } from 'node:path'
|
||||
import * as yaml from 'js-yaml'
|
||||
import ts from 'typescript'
|
||||
|
||||
interface JsExpr {
|
||||
__jsExpr: string
|
||||
}
|
||||
|
||||
interface PackageManifest {
|
||||
name?: string
|
||||
dependencies?: Record<string, string>
|
||||
}
|
||||
|
||||
interface PluginReference {
|
||||
file: string
|
||||
name: string
|
||||
}
|
||||
|
||||
const root = resolve(import.meta.dirname, '..')
|
||||
const metadataFields = ['id', 'name', 'group', 'disabled', 'inject', 'intercept', 'isolate'] as const
|
||||
const jsExprType = new yaml.Type('tag:yaml.org,2002:js', {
|
||||
@@ -30,6 +44,7 @@ const files = globSync(['**/*cordis*.yml', '**/*cordis*.yaml'], {
|
||||
exclude: ['.claude/**', 'node_modules/**', 'vendor/**'],
|
||||
}).sort()
|
||||
const errors: string[] = []
|
||||
const examplePluginReferences: PluginReference[] = []
|
||||
|
||||
for (const file of files) {
|
||||
const document: unknown = yaml.load(readFileSync(resolve(root, file), 'utf8'), { schema })
|
||||
@@ -42,8 +57,10 @@ for (const file of files) {
|
||||
}
|
||||
}
|
||||
|
||||
errors.push(...validateExampleResolution())
|
||||
|
||||
if (errors.length > 0) {
|
||||
console.error('verify-cordis-config: Loader entry metadata is static; move !!js under plugin config or select an explicit overlay.')
|
||||
console.error('verify-cordis-config: invalid Loader metadata or example package resolution:')
|
||||
for (const error of errors) console.error(`- ${error}`)
|
||||
process.exitCode = 1
|
||||
} else {
|
||||
@@ -55,6 +72,7 @@ function validateEntry(value: unknown, file: string, path: string): void {
|
||||
errors.push(`${file}${path}: entry must be an object`)
|
||||
return
|
||||
}
|
||||
recordExamplePlugin(value, file)
|
||||
validateMetadata(value, file, path)
|
||||
if ((value.group === true || value.name === '@cordisjs/plugin-group') && isUnknownArray(value.config)) {
|
||||
for (let index = 0; index < value.config.length; index++) {
|
||||
@@ -68,6 +86,7 @@ function validateEntry(value: unknown, file: string, path: string): void {
|
||||
const patch = config.patches[index]
|
||||
const patchPath = `${path}.config.patches[${index}]`
|
||||
if (!isRecord(patch)) continue
|
||||
recordExamplePlugin(patch, file)
|
||||
validateMetadata(patch, file, patchPath)
|
||||
if (!isUnknownArray(patch.insert)) continue
|
||||
for (let insertIndex = 0; insertIndex < patch.insert.length; insertIndex++) {
|
||||
@@ -76,6 +95,83 @@ function validateEntry(value: unknown, file: string, path: string): void {
|
||||
}
|
||||
}
|
||||
|
||||
function recordExamplePlugin(entry: Record<string, unknown>, file: string): void {
|
||||
if (file.startsWith('examples/') && typeof entry.name === 'string') {
|
||||
examplePluginReferences.push({ file, name: entry.name })
|
||||
}
|
||||
}
|
||||
|
||||
function validateExampleResolution(): string[] {
|
||||
const violations: string[] = []
|
||||
const exampleManifest = readManifest('examples/package.json')
|
||||
const dependencies = exampleManifest.dependencies ?? {}
|
||||
const localPackages = localPackageDirectories()
|
||||
const rootReferences = rootProjectReferences()
|
||||
const requiredPackages = new Map<string, Set<string>>()
|
||||
|
||||
for (const reference of examplePluginReferences) {
|
||||
const packageName = packageNameFromSpecifier(reference.name)
|
||||
if (packageName === undefined) continue
|
||||
const locations = requiredPackages.get(packageName) ?? new Set<string>()
|
||||
locations.add(reference.file)
|
||||
requiredPackages.set(packageName, locations)
|
||||
}
|
||||
|
||||
for (const [packageName, locations] of requiredPackages) {
|
||||
if (!(packageName in dependencies)) {
|
||||
violations.push(`${[...locations].join(', ')}: ${packageName} must be declared in examples/package.json dependencies`)
|
||||
}
|
||||
}
|
||||
|
||||
const localExamplePackages = new Set([
|
||||
...Object.keys(dependencies),
|
||||
...requiredPackages.keys(),
|
||||
])
|
||||
for (const packageName of localExamplePackages) {
|
||||
const packageDirectory = localPackages.get(packageName)
|
||||
if (packageDirectory === undefined || rootReferences.has(packageDirectory)) continue
|
||||
const repoPath = relative(root, packageDirectory).replaceAll('\\', '/')
|
||||
violations.push(`tsconfig.json: missing project reference for ${packageName} (${repoPath})`)
|
||||
}
|
||||
|
||||
return violations
|
||||
}
|
||||
|
||||
function readManifest(path: string): PackageManifest {
|
||||
return JSON.parse(readFileSync(resolve(root, path), 'utf8')) as PackageManifest
|
||||
}
|
||||
|
||||
function localPackageDirectories(): Map<string, string> {
|
||||
const manifests = globSync(['packages/*/*/package.json', 'vendor/*/package.json'], { cwd: root })
|
||||
const packages = new Map<string, string>()
|
||||
for (const manifestPath of manifests) {
|
||||
const manifest = readManifest(manifestPath)
|
||||
if (manifest.name !== undefined) packages.set(manifest.name, resolve(root, dirname(manifestPath)))
|
||||
}
|
||||
return packages
|
||||
}
|
||||
|
||||
function rootProjectReferences(): Set<string> {
|
||||
const config = ts.readConfigFile(resolve(root, 'tsconfig.json'), path => ts.sys.readFile(path))
|
||||
if (config.error !== undefined) {
|
||||
throw new Error(ts.flattenDiagnosticMessageText(config.error.messageText, '\n'))
|
||||
}
|
||||
const references = (config.config as { references?: Array<{ path?: unknown }> }).references ?? []
|
||||
return new Set(references.flatMap((reference) => {
|
||||
if (typeof reference.path !== 'string') return []
|
||||
return [resolve(root, reference.path)]
|
||||
}))
|
||||
}
|
||||
|
||||
function packageNameFromSpecifier(specifier: string): string | undefined {
|
||||
if (specifier.startsWith('.') || specifier.startsWith('/') || specifier.startsWith('file:')) return undefined
|
||||
const segments = specifier.split('/')
|
||||
if (specifier.startsWith('@')) {
|
||||
return segments.length >= 2 ? `${segments[0]}/${segments[1]}` : undefined
|
||||
}
|
||||
return segments[0] || undefined
|
||||
}
|
||||
|
||||
function validateMetadata(entry: Record<string, unknown>, file: string, path: string): void {
|
||||
for (const field of metadataFields) {
|
||||
if (!(field in entry)) continue
|
||||
|
||||
@@ -48,6 +48,7 @@ const SENTENCE_MODEL_EXPERIENCE: Readonly<Record<string, SentenceContract>> = {
|
||||
'packages/fs/fs-local': { kind: 'indirect', reason: 'The provider backend delegates model rendering to dsh-tool-fs.' },
|
||||
'packages/hooks/hook-protocol': { kind: 'indirect', reason: 'Only the hook bridge plugins render decoded hook output to a model.' },
|
||||
'packages/llm/llm': { kind: 'none', reason: 'The adapter registry forwards already-assembled requests unchanged.' },
|
||||
'packages/llm/token-meter': { kind: 'indirect', reason: 'The measurement service leaves model-visible changes to its consumers.' },
|
||||
'packages/sandbox/sandbox-local': { kind: 'indirect', reason: 'The provider backend delegates model rendering to dsh-bash-sandbox and dsh-tool-bash.' },
|
||||
'packages/sdk/create-sdk': { kind: 'indirect', reason: 'The initializer only writes project files; selected runtime plugins provide the generated project model surface.' },
|
||||
'packages/sdk/helper': { kind: 'none', reason: 'The project domain edits files and registers no live agent or model surface.' },
|
||||
|
||||
Reference in New Issue
Block a user