@cjhyy/code-shell-core 0.5.0-rc.0 → 0.5.0-rc.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent/agent-definition-registry.d.ts +29 -0
- package/dist/agent/agent-definition-registry.js +74 -0
- package/dist/agent/agent-definition.d.ts +37 -0
- package/dist/agent/agent-definition.js +59 -0
- package/dist/arena/arena.js +11 -12
- package/dist/arena/context/context-tools.js +2 -4
- package/dist/arena/context/within-root.d.ts +7 -0
- package/dist/arena/context/within-root.js +15 -0
- package/dist/arena/iterate/phases/argue.js +8 -2
- package/dist/arena/iterate/phases/revise.js +1 -1
- package/dist/arena/iterate/phases/tournament.js +3 -3
- package/dist/arena/phases/adjudication.js +1 -4
- package/dist/arena/phases/build-consensus.js +1 -4
- package/dist/arena/phases/cross-review.js +2 -8
- package/dist/arena/phases/debate-rounds.js +1 -4
- package/dist/arena/phases/participant-research.js +1 -4
- package/dist/arena/phases/planning-detail-expansion.js +8 -10
- package/dist/arena/planner.js +0 -1
- package/dist/arena/providers/docs.d.ts +1 -0
- package/dist/arena/providers/docs.js +5 -2
- package/dist/arena/providers/repo.d.ts +1 -0
- package/dist/arena/providers/repo.js +5 -2
- package/dist/arena/strategies/utils.js +36 -3
- package/dist/arena/transitions.d.ts +7 -2
- package/dist/arena/transitions.js +9 -4
- package/dist/arena/types.d.ts +8 -2
- package/dist/automation/cron-expr.d.ts +31 -0
- package/dist/automation/cron-expr.js +151 -0
- package/dist/automation/index.d.ts +41 -0
- package/dist/automation/index.js +39 -0
- package/dist/automation/runner.d.ts +67 -0
- package/dist/automation/runner.js +56 -0
- package/dist/automation/scheduler.d.ts +156 -0
- package/dist/automation/scheduler.js +546 -0
- package/dist/automation/store.d.ts +39 -0
- package/dist/automation/store.js +119 -0
- package/dist/automation/write-policy.d.ts +34 -0
- package/dist/automation/write-policy.js +113 -0
- package/dist/automation/write-run.d.ts +51 -0
- package/dist/automation/write-run.js +38 -0
- package/dist/capability-control/index.d.ts +5 -0
- package/dist/capability-control/index.js +3 -0
- package/dist/capability-control/overlay.d.ts +49 -0
- package/dist/capability-control/overlay.js +89 -0
- package/dist/capability-control/project.d.ts +65 -0
- package/dist/capability-control/project.js +147 -0
- package/dist/capability-control/service.d.ts +70 -0
- package/dist/capability-control/service.js +147 -0
- package/dist/capability-control/types.d.ts +62 -0
- package/dist/capability-control/types.js +17 -0
- package/dist/cli/agent-server-stdio.d.ts +12 -1
- package/dist/cli/agent-server-stdio.js +118 -18
- package/dist/cli/agent-server-tcp.d.ts +20 -0
- package/dist/cli/agent-server-tcp.js +120 -0
- package/dist/cli/graceful-shutdown.d.ts +27 -0
- package/dist/cli/graceful-shutdown.js +30 -0
- package/dist/context/compaction.js +23 -15
- package/dist/context/manager.d.ts +0 -20
- package/dist/context/manager.js +0 -37
- package/dist/cron/cron-runtime.d.ts +2 -0
- package/dist/cron/cron-runtime.js +2 -0
- package/dist/cron/cron-store.d.ts +2 -0
- package/dist/cron/cron-store.js +2 -0
- package/dist/cron/scheduler.d.ts +5 -32
- package/dist/cron/scheduler.js +5 -113
- package/dist/data/openrouter-sync.js +1 -1
- package/dist/engine/engine.d.ts +256 -16
- package/dist/engine/engine.js +856 -200
- package/dist/engine/goal.d.ts +42 -0
- package/dist/engine/goal.js +52 -0
- package/dist/engine/image-compression.d.ts +81 -0
- package/dist/engine/image-compression.js +186 -0
- package/dist/engine/image-policy.d.ts +152 -0
- package/dist/engine/image-policy.js +165 -0
- package/dist/engine/reactive-threshold.d.ts +13 -0
- package/dist/engine/reactive-threshold.js +17 -0
- package/dist/engine/runtime.js +7 -0
- package/dist/engine/session-title.d.ts +13 -0
- package/dist/engine/session-title.js +34 -0
- package/dist/engine/turn-loop.d.ts +29 -8
- package/dist/engine/turn-loop.js +498 -316
- package/dist/git/parse-log.d.ts +13 -0
- package/dist/git/parse-log.js +21 -0
- package/dist/git/utils.d.ts +8 -6
- package/dist/git/utils.js +57 -26
- package/dist/git/worktree.js +33 -18
- package/dist/hooks/events.d.ts +23 -1
- package/dist/hooks/goal-stop-hook.d.ts +30 -0
- package/dist/hooks/goal-stop-hook.js +102 -0
- package/dist/hooks/hook-output.d.ts +33 -0
- package/dist/hooks/hook-output.js +79 -0
- package/dist/hooks/registry.d.ts +7 -0
- package/dist/hooks/registry.js +38 -1
- package/dist/hooks/shell-runner.d.ts +2 -0
- package/dist/hooks/shell-runner.js +43 -2
- package/dist/index.d.ts +32 -7
- package/dist/index.js +30 -3
- package/dist/llm/capabilities/reasoning-control.d.ts +24 -0
- package/dist/llm/capabilities/reasoning-control.js +38 -0
- package/dist/llm/capabilities/rules.js +33 -3
- package/dist/llm/capabilities/types.d.ts +22 -2
- package/dist/llm/clamp-max-tokens.d.ts +11 -0
- package/dist/llm/clamp-max-tokens.js +17 -0
- package/dist/llm/client-base.d.ts +41 -4
- package/dist/llm/client-base.js +67 -13
- package/dist/llm/client-factory.d.ts +3 -3
- package/dist/llm/client-factory.js +2 -2
- package/dist/llm/model-pool.d.ts +15 -11
- package/dist/llm/model-pool.js +23 -20
- package/dist/llm/provider-catalog.d.ts +2 -2
- package/dist/llm/providers/anthropic.d.ts +32 -2
- package/dist/llm/providers/anthropic.js +147 -8
- package/dist/llm/providers/openai.d.ts +11 -2
- package/dist/llm/providers/openai.js +253 -52
- package/dist/llm/reasoning-setting.d.ts +51 -0
- package/dist/llm/reasoning-setting.js +32 -0
- package/dist/llm/stop-reason.d.ts +13 -0
- package/dist/llm/stop-reason.js +21 -0
- package/dist/llm/strip-vision.d.ts +32 -0
- package/dist/llm/strip-vision.js +94 -0
- package/dist/llm/types.d.ts +5 -6
- package/dist/logging/logger.js +18 -4
- package/dist/logging/sanitize-messages.d.ts +10 -0
- package/dist/logging/sanitize-messages.js +90 -0
- package/dist/lsp/client.js +28 -8
- package/dist/lsp/manager.js +2 -1
- package/dist/lsp/root-path.d.ts +9 -0
- package/dist/lsp/root-path.js +12 -0
- package/dist/onboarding.js +18 -11
- package/dist/plugins/gitOps.d.ts +34 -0
- package/dist/plugins/gitOps.js +59 -3
- package/dist/plugins/installer/codex/convertAgents.d.ts +7 -0
- package/dist/plugins/installer/codex/convertAgents.js +55 -0
- package/dist/plugins/installer/codex/convertMcp.d.ts +8 -0
- package/dist/plugins/installer/codex/convertMcp.js +45 -0
- package/dist/plugins/installer/codex/convertSkills.d.ts +12 -0
- package/dist/plugins/installer/codex/convertSkills.js +33 -0
- package/dist/plugins/installer/detectFormat.d.ts +2 -0
- package/dist/plugins/installer/detectFormat.js +6 -0
- package/dist/plugins/installer/install.d.ts +7 -0
- package/dist/plugins/installer/install.js +99 -0
- package/dist/plugins/installer/installFromSource.d.ts +13 -0
- package/dist/plugins/installer/installFromSource.js +45 -0
- package/dist/plugins/installer/list.d.ts +9 -0
- package/dist/plugins/installer/list.js +30 -0
- package/dist/plugins/installer/loadPluginAgents.d.ts +3 -0
- package/dist/plugins/installer/loadPluginAgents.js +23 -0
- package/dist/plugins/installer/loadPluginMcp.d.ts +7 -0
- package/dist/plugins/installer/loadPluginMcp.js +74 -0
- package/dist/plugins/installer/parseSource.d.ts +20 -0
- package/dist/plugins/installer/parseSource.js +74 -0
- package/dist/plugins/installer/paths.d.ts +5 -0
- package/dist/plugins/installer/paths.js +25 -0
- package/dist/plugins/installer/types.d.ts +49 -0
- package/dist/plugins/installer/types.js +27 -0
- package/dist/plugins/installer/uninstall.d.ts +2 -0
- package/dist/plugins/installer/uninstall.js +14 -0
- package/dist/plugins/installer/update.d.ts +14 -0
- package/dist/plugins/installer/update.js +65 -0
- package/dist/plugins/loadPluginHooks.d.ts +8 -1
- package/dist/plugins/loadPluginHooks.js +11 -1
- package/dist/plugins/marketplaceManager.d.ts +8 -1
- package/dist/plugins/marketplaceManager.js +38 -4
- package/dist/plugins/pluginCommandHook.js +34 -0
- package/dist/plugins/pluginInstaller.d.ts +21 -0
- package/dist/plugins/pluginInstaller.js +128 -29
- package/dist/plugins/schemas.js +40 -6
- package/dist/plugins/types.d.ts +4 -0
- package/dist/preset/index.js +16 -0
- package/dist/prompt/composer.d.ts +10 -0
- package/dist/prompt/composer.js +29 -4
- package/dist/prompt/sections/orchestration.md +8 -0
- package/dist/protocol/chat-session-manager.d.ts +8 -1
- package/dist/protocol/chat-session-manager.js +10 -0
- package/dist/protocol/chat-session.d.ts +17 -0
- package/dist/protocol/chat-session.js +28 -0
- package/dist/protocol/redact.d.ts +50 -0
- package/dist/protocol/redact.js +71 -0
- package/dist/protocol/server.d.ts +41 -2
- package/dist/protocol/server.js +231 -33
- package/dist/protocol/tcp-transport.d.ts +45 -0
- package/dist/protocol/tcp-transport.js +74 -0
- package/dist/protocol/types.d.ts +23 -0
- package/dist/protocol/types.js +6 -0
- package/dist/remote/bridge.d.ts +11 -2
- package/dist/remote/bridge.js +61 -33
- package/dist/run/ArtifactTracker.js +5 -4
- package/dist/run/EngineRunner.d.ts +27 -1
- package/dist/run/EngineRunner.js +46 -8
- package/dist/run/FileRunStore.js +32 -8
- package/dist/run/RunLock.d.ts +28 -2
- package/dist/run/RunLock.js +49 -7
- package/dist/run/RunManager.js +51 -3
- package/dist/run/RunQueue.d.ts +1 -0
- package/dist/run/RunQueue.js +8 -2
- package/dist/run/factory.d.ts +7 -0
- package/dist/run/factory.js +1 -0
- package/dist/run/index.d.ts +2 -2
- package/dist/run/index.js +1 -1
- package/dist/run/redirect-target.d.ts +7 -0
- package/dist/run/redirect-target.js +13 -0
- package/dist/run/types.js +1 -1
- package/dist/services/browser-open.d.ts +13 -0
- package/dist/services/browser-open.js +16 -0
- package/dist/services/dream-consolidation.d.ts +57 -0
- package/dist/services/dream-consolidation.js +151 -0
- package/dist/services/memory-orchestrator.js +18 -0
- package/dist/services/notifier.d.ts +8 -0
- package/dist/services/notifier.js +39 -25
- package/dist/services/oauth.js +5 -7
- package/dist/services/session-memory-sort.d.ts +8 -0
- package/dist/services/session-memory-sort.js +9 -0
- package/dist/services/session-memory.js +8 -9
- package/dist/session/file-history.js +10 -3
- package/dist/session/session-manager.d.ts +23 -1
- package/dist/session/session-manager.js +73 -2
- package/dist/settings/disk-defaults.d.ts +35 -0
- package/dist/settings/disk-defaults.js +24 -0
- package/dist/settings/manager.d.ts +45 -1
- package/dist/settings/manager.js +121 -17
- package/dist/settings/manager.test.d.ts +1 -0
- package/dist/settings/manager.test.js +73 -0
- package/dist/settings/personalization.d.ts +19 -0
- package/dist/settings/personalization.js +7 -0
- package/dist/settings/schema.d.ts +1163 -126
- package/dist/settings/schema.js +126 -10
- package/dist/skills/scanner.d.ts +13 -6
- package/dist/skills/scanner.js +23 -4
- package/dist/tool-system/builtin/add-marketplace.d.ts +12 -0
- package/dist/tool-system/builtin/add-marketplace.js +76 -0
- package/dist/tool-system/builtin/agent-registry.d.ts +15 -0
- package/dist/tool-system/builtin/agent-registry.js +14 -4
- package/dist/tool-system/builtin/agent.d.ts +55 -0
- package/dist/tool-system/builtin/agent.js +165 -17
- package/dist/tool-system/builtin/apply-patch/index.js +22 -3
- package/dist/tool-system/builtin/arena.js +6 -7
- package/dist/tool-system/builtin/complete-goal.d.ts +25 -0
- package/dist/tool-system/builtin/complete-goal.js +45 -0
- package/dist/tool-system/builtin/cron.js +53 -9
- package/dist/tool-system/builtin/edit.d.ts +2 -1
- package/dist/tool-system/builtin/edit.js +6 -1
- package/dist/tool-system/builtin/generate-image.d.ts +25 -0
- package/dist/tool-system/builtin/generate-image.js +146 -0
- package/dist/tool-system/builtin/glob.js +7 -0
- package/dist/tool-system/builtin/grep.js +6 -0
- package/dist/tool-system/builtin/index.d.ts +18 -1
- package/dist/tool-system/builtin/index.js +62 -1
- package/dist/tool-system/builtin/notebook-edit.d.ts +2 -1
- package/dist/tool-system/builtin/notebook-edit.js +8 -1
- package/dist/tool-system/builtin/read.d.ts +2 -1
- package/dist/tool-system/builtin/read.js +9 -1
- package/dist/tool-system/builtin/skill.js +23 -5
- package/dist/tool-system/builtin/sleep.js +9 -3
- package/dist/tool-system/builtin/task.js +4 -1
- package/dist/tool-system/builtin/update-automation-memory.d.ts +24 -0
- package/dist/tool-system/builtin/update-automation-memory.js +60 -0
- package/dist/tool-system/builtin/view-image.d.ts +18 -0
- package/dist/tool-system/builtin/view-image.js +96 -0
- package/dist/tool-system/builtin/web-search.d.ts +4 -1
- package/dist/tool-system/builtin/web-search.js +36 -3
- package/dist/tool-system/builtin/write.d.ts +2 -1
- package/dist/tool-system/builtin/write.js +7 -1
- package/dist/tool-system/context.d.ts +43 -0
- package/dist/tool-system/executor.d.ts +0 -1
- package/dist/tool-system/executor.js +34 -40
- package/dist/tool-system/investigation-guard.d.ts +4 -1
- package/dist/tool-system/investigation-guard.js +19 -3
- package/dist/tool-system/mcp-manager.d.ts +36 -12
- package/dist/tool-system/mcp-manager.js +173 -10
- package/dist/tool-system/path-policy.d.ts +94 -0
- package/dist/tool-system/path-policy.js +279 -0
- package/dist/tool-system/permission.js +43 -50
- package/dist/tool-system/plan-mode-allowlist.d.ts +21 -0
- package/dist/tool-system/plan-mode-allowlist.js +43 -0
- package/dist/tool-system/registry.js +12 -1
- package/dist/tool-system/validation.d.ts +10 -0
- package/dist/tool-system/validation.js +16 -3
- package/dist/types.d.ts +90 -15
- package/dist/utils/format.js +4 -2
- package/dist/utils/lockfile.js +9 -2
- package/dist/utils/theme.d.ts +6 -0
- package/dist/utils/theme.js +4 -2
- package/package.json +2 -1
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { type AgentDefinition } from "./agent-definition.js";
|
|
2
|
+
export interface AgentSourceDir {
|
|
3
|
+
dir: string;
|
|
4
|
+
source: "project" | "user" | "plugin";
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Loads reusable sub-agent role definitions from one or more directories
|
|
8
|
+
* of `*.md` files. Non-recursive. Malformed files are skipped with a
|
|
9
|
+
* warning rather than failing the whole load — one bad role file must
|
|
10
|
+
* not break the agent system.
|
|
11
|
+
*
|
|
12
|
+
* When multiple dirs define the same role name, the LAST dir wins. This is
|
|
13
|
+
* pure mechanism — the project>user POLICY lives in the caller's dir ORDER
|
|
14
|
+
* (loadAgentDefinitionsForCwd passes [user, ...plugins, project] so a repo's
|
|
15
|
+
* in-tree agent wins; see spec §7.2). The shadowing def is marked
|
|
16
|
+
* `override: true` and records the sources it shadowed in `shadowedSources`.
|
|
17
|
+
* Names in `disabled` are filtered out entirely after the merge, so the LLM
|
|
18
|
+
* never sees them.
|
|
19
|
+
*/
|
|
20
|
+
export declare class AgentDefinitionRegistry {
|
|
21
|
+
private defs;
|
|
22
|
+
readonly warnings: string[];
|
|
23
|
+
/** Back-compat single-dir loader (project source, no disabled filter). */
|
|
24
|
+
static loadFromDir(dir: string): AgentDefinitionRegistry;
|
|
25
|
+
static loadFromDirs(dirs: AgentSourceDir[], disabled: string[]): AgentDefinitionRegistry;
|
|
26
|
+
has(name: string): boolean;
|
|
27
|
+
get(name: string): AgentDefinition | undefined;
|
|
28
|
+
list(): AgentDefinition[];
|
|
29
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { readdirSync, readFileSync, existsSync, statSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { parseAgentDefinition } from "./agent-definition.js";
|
|
4
|
+
/**
|
|
5
|
+
* Loads reusable sub-agent role definitions from one or more directories
|
|
6
|
+
* of `*.md` files. Non-recursive. Malformed files are skipped with a
|
|
7
|
+
* warning rather than failing the whole load — one bad role file must
|
|
8
|
+
* not break the agent system.
|
|
9
|
+
*
|
|
10
|
+
* When multiple dirs define the same role name, the LAST dir wins. This is
|
|
11
|
+
* pure mechanism — the project>user POLICY lives in the caller's dir ORDER
|
|
12
|
+
* (loadAgentDefinitionsForCwd passes [user, ...plugins, project] so a repo's
|
|
13
|
+
* in-tree agent wins; see spec §7.2). The shadowing def is marked
|
|
14
|
+
* `override: true` and records the sources it shadowed in `shadowedSources`.
|
|
15
|
+
* Names in `disabled` are filtered out entirely after the merge, so the LLM
|
|
16
|
+
* never sees them.
|
|
17
|
+
*/
|
|
18
|
+
export class AgentDefinitionRegistry {
|
|
19
|
+
defs = new Map();
|
|
20
|
+
warnings = [];
|
|
21
|
+
/** Back-compat single-dir loader (project source, no disabled filter). */
|
|
22
|
+
static loadFromDir(dir) {
|
|
23
|
+
return AgentDefinitionRegistry.loadFromDirs([{ dir, source: "project" }], []);
|
|
24
|
+
}
|
|
25
|
+
static loadFromDirs(dirs, disabled) {
|
|
26
|
+
const reg = new AgentDefinitionRegistry();
|
|
27
|
+
for (const { dir, source } of dirs) {
|
|
28
|
+
if (!existsSync(dir) || !statSync(dir).isDirectory())
|
|
29
|
+
continue;
|
|
30
|
+
for (const entry of readdirSync(dir).sort()) {
|
|
31
|
+
if (!entry.endsWith(".md"))
|
|
32
|
+
continue;
|
|
33
|
+
const full = join(dir, entry);
|
|
34
|
+
try {
|
|
35
|
+
if (!statSync(full).isFile())
|
|
36
|
+
continue;
|
|
37
|
+
const def = parseAgentDefinition(readFileSync(full, "utf8"), entry);
|
|
38
|
+
def.source = source;
|
|
39
|
+
def.filePath = full;
|
|
40
|
+
// A later dir overriding an earlier one. Carry forward the shadowed
|
|
41
|
+
// sources (the prev def's own source + anything it already shadowed)
|
|
42
|
+
// so a project def that shadows a plugin def that shadowed a user
|
|
43
|
+
// def reports both.
|
|
44
|
+
const prev = reg.defs.get(def.name);
|
|
45
|
+
if (prev) {
|
|
46
|
+
def.override = true;
|
|
47
|
+
const shadowed = new Set(prev.shadowedSources ?? []);
|
|
48
|
+
if (prev.source)
|
|
49
|
+
shadowed.add(prev.source);
|
|
50
|
+
def.shadowedSources = [...shadowed];
|
|
51
|
+
}
|
|
52
|
+
reg.defs.set(def.name, def);
|
|
53
|
+
}
|
|
54
|
+
catch (err) {
|
|
55
|
+
reg.warnings.push(`${entry}: ${err.message}`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
// Filter disabled roles after the merge so a user override of a
|
|
60
|
+
// disabled name is still removed.
|
|
61
|
+
for (const name of new Set(disabled))
|
|
62
|
+
reg.defs.delete(name);
|
|
63
|
+
return reg;
|
|
64
|
+
}
|
|
65
|
+
has(name) {
|
|
66
|
+
return this.defs.has(name);
|
|
67
|
+
}
|
|
68
|
+
get(name) {
|
|
69
|
+
return this.defs.get(name);
|
|
70
|
+
}
|
|
71
|
+
list() {
|
|
72
|
+
return [...this.defs.values()];
|
|
73
|
+
}
|
|
74
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/** A reusable sub-agent role, loaded from a Markdown file. */
|
|
2
|
+
export interface AgentDefinition {
|
|
3
|
+
/** Unique role key, e.g. "researcher". Matched against Agent({ agent_type }). */
|
|
4
|
+
name: string;
|
|
5
|
+
/** Human-facing summary of when to use this role. */
|
|
6
|
+
description: string;
|
|
7
|
+
/** Optional ModelPool key (e.g. "flash"). Undefined → inherit parent model. */
|
|
8
|
+
model?: string;
|
|
9
|
+
/** Optional turn cap for this role. Undefined → caller/default decides. */
|
|
10
|
+
maxTurns?: number;
|
|
11
|
+
/** Optional tool allowlist. Undefined → inherit parent's full tool set. */
|
|
12
|
+
tools?: string[];
|
|
13
|
+
/** Markdown body — becomes the child Engine's appendSystemPrompt. */
|
|
14
|
+
systemPrompt: string;
|
|
15
|
+
/** Where this def was loaded from. Runtime-only; never serialized. */
|
|
16
|
+
source?: "project" | "user" | "plugin";
|
|
17
|
+
/** Absolute path of the file it came from. Runtime-only. */
|
|
18
|
+
filePath?: string;
|
|
19
|
+
/** True when this def shadows a same-named def from a lower-priority dir. */
|
|
20
|
+
override?: boolean;
|
|
21
|
+
/** Sources whose same-named def this one shadows (runtime-only). Drives the
|
|
22
|
+
* UI "this project overrides your user version" warning (spec §7.2). */
|
|
23
|
+
shadowedSources?: Array<"project" | "user" | "plugin">;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Parse a Markdown agent-definition file (YAML frontmatter + body).
|
|
27
|
+
* Pure: no filesystem access. `sourceName` is only used in error messages.
|
|
28
|
+
*/
|
|
29
|
+
export declare function parseAgentDefinition(raw: string, sourceName: string): AgentDefinition;
|
|
30
|
+
/**
|
|
31
|
+
* Serialize an AgentDefinition back to a Markdown file body (YAML
|
|
32
|
+
* frontmatter + system prompt). Inverse of parseAgentDefinition.
|
|
33
|
+
* Optional fields that are unset are omitted entirely so an inheriting
|
|
34
|
+
* role (e.g. model undefined → inherit parent) stays clean on disk.
|
|
35
|
+
* Runtime-only metadata (source/filePath/override) is never written.
|
|
36
|
+
*/
|
|
37
|
+
export declare function serializeAgentDefinition(def: AgentDefinition): string;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
|
|
2
|
+
/**
|
|
3
|
+
* Parse a Markdown agent-definition file (YAML frontmatter + body).
|
|
4
|
+
* Pure: no filesystem access. `sourceName` is only used in error messages.
|
|
5
|
+
*/
|
|
6
|
+
export function parseAgentDefinition(raw, sourceName) {
|
|
7
|
+
const match = /^---\n([\s\S]*?)\n---\n?([\s\S]*)$/.exec(raw.trim());
|
|
8
|
+
if (!match) {
|
|
9
|
+
throw new Error(`${sourceName}: missing YAML frontmatter (expected leading '---' block)`);
|
|
10
|
+
}
|
|
11
|
+
const [, yamlSrc, body] = match;
|
|
12
|
+
let fm;
|
|
13
|
+
try {
|
|
14
|
+
fm = (parseYaml(yamlSrc) ?? {});
|
|
15
|
+
}
|
|
16
|
+
catch (err) {
|
|
17
|
+
throw new Error(`${sourceName}: invalid YAML frontmatter — ${err.message}`, { cause: err });
|
|
18
|
+
}
|
|
19
|
+
if (typeof fm.name !== "string" || fm.name.trim().length === 0) {
|
|
20
|
+
throw new Error(`${sourceName}: frontmatter must include a non-empty 'name'`);
|
|
21
|
+
}
|
|
22
|
+
if (typeof fm.description !== "string" || fm.description.trim().length === 0) {
|
|
23
|
+
throw new Error(`${sourceName}: frontmatter must include a non-empty 'description'`);
|
|
24
|
+
}
|
|
25
|
+
const def = {
|
|
26
|
+
name: fm.name.trim(),
|
|
27
|
+
description: fm.description.trim(),
|
|
28
|
+
systemPrompt: body.trim(),
|
|
29
|
+
};
|
|
30
|
+
if (typeof fm.model === "string" && fm.model.trim())
|
|
31
|
+
def.model = fm.model.trim();
|
|
32
|
+
if (typeof fm.maxTurns === "number")
|
|
33
|
+
def.maxTurns = fm.maxTurns;
|
|
34
|
+
if (Array.isArray(fm.tools)) {
|
|
35
|
+
def.tools = fm.tools.filter((t) => typeof t === "string");
|
|
36
|
+
}
|
|
37
|
+
return def;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Serialize an AgentDefinition back to a Markdown file body (YAML
|
|
41
|
+
* frontmatter + system prompt). Inverse of parseAgentDefinition.
|
|
42
|
+
* Optional fields that are unset are omitted entirely so an inheriting
|
|
43
|
+
* role (e.g. model undefined → inherit parent) stays clean on disk.
|
|
44
|
+
* Runtime-only metadata (source/filePath/override) is never written.
|
|
45
|
+
*/
|
|
46
|
+
export function serializeAgentDefinition(def) {
|
|
47
|
+
const fm = {
|
|
48
|
+
name: def.name,
|
|
49
|
+
description: def.description,
|
|
50
|
+
};
|
|
51
|
+
if (def.model !== undefined)
|
|
52
|
+
fm.model = def.model;
|
|
53
|
+
if (def.maxTurns !== undefined)
|
|
54
|
+
fm.maxTurns = def.maxTurns;
|
|
55
|
+
if (def.tools !== undefined)
|
|
56
|
+
fm.tools = def.tools;
|
|
57
|
+
const yaml = stringifyYaml(fm).trimEnd();
|
|
58
|
+
return `---\n${yaml}\n---\n${def.systemPrompt}\n`;
|
|
59
|
+
}
|
package/dist/arena/arena.js
CHANGED
|
@@ -28,6 +28,15 @@ import { runDetailExpansion } from "./phases/planning-detail-expansion.js";
|
|
|
28
28
|
import { withLanguage } from "./strategies/language-wrapper.js";
|
|
29
29
|
import { ArenaLedger } from "./ledger.js";
|
|
30
30
|
import { registerClaims } from "./phases/claim-registry.js";
|
|
31
|
+
/** Bucket claims by status. Shared by the two consensus phases. */
|
|
32
|
+
function summarizeClaims(all) {
|
|
33
|
+
return {
|
|
34
|
+
verified: all.filter((c) => c.status === "verified"),
|
|
35
|
+
contested: all.filter((c) => c.status === "contested"),
|
|
36
|
+
unresolved: all.filter((c) => c.status === "unresolved"),
|
|
37
|
+
rejected: all.filter((c) => c.status === "rejected"),
|
|
38
|
+
};
|
|
39
|
+
}
|
|
31
40
|
export class Arena {
|
|
32
41
|
config;
|
|
33
42
|
strategy;
|
|
@@ -192,12 +201,7 @@ export class Arena {
|
|
|
192
201
|
signal?.throwIfAborted();
|
|
193
202
|
// ─── Roadmap Consensus ───────────────────────────────────
|
|
194
203
|
const allClaims = ledger.getAllClaims();
|
|
195
|
-
const claimSummary =
|
|
196
|
-
verified: allClaims.filter((c) => c.status === "verified"),
|
|
197
|
-
contested: allClaims.filter((c) => c.status === "contested"),
|
|
198
|
-
unresolved: allClaims.filter((c) => c.status === "unresolved"),
|
|
199
|
-
rejected: allClaims.filter((c) => c.status === "rejected"),
|
|
200
|
-
};
|
|
204
|
+
const claimSummary = summarizeClaims(allClaims);
|
|
201
205
|
logger.info("arena.planning_consensus_phase", { concluder: concluder.name });
|
|
202
206
|
const consensus = await buildConsensus({
|
|
203
207
|
concluder,
|
|
@@ -295,12 +299,7 @@ export class Arena {
|
|
|
295
299
|
signal?.throwIfAborted();
|
|
296
300
|
// ─── Consensus (claim-aware) ─────────────────────────────
|
|
297
301
|
const allClaims = ledger.getAllClaims();
|
|
298
|
-
const claimSummary =
|
|
299
|
-
verified: allClaims.filter((c) => c.status === "verified"),
|
|
300
|
-
contested: allClaims.filter((c) => c.status === "contested"),
|
|
301
|
-
unresolved: allClaims.filter((c) => c.status === "unresolved"),
|
|
302
|
-
rejected: allClaims.filter((c) => c.status === "rejected"),
|
|
303
|
-
};
|
|
302
|
+
const claimSummary = summarizeClaims(allClaims);
|
|
304
303
|
logger.info("arena.consensus_phase", {
|
|
305
304
|
concluder: concluder.name,
|
|
306
305
|
verified: claimSummary.verified.length,
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
import { readFileSync, existsSync, readdirSync, statSync } from "node:fs";
|
|
10
10
|
import { execFileSync } from "node:child_process";
|
|
11
11
|
import { resolve, join } from "node:path";
|
|
12
|
+
import { isWithinRoot } from "./within-root.js";
|
|
12
13
|
export const MAX_TOOL_RESULT = 15_000; // chars per tool call
|
|
13
14
|
// 5 was too generous for thinking-mode models — they'd spend the
|
|
14
15
|
// entire budget on read_file with textLen=0, then need an
|
|
@@ -112,10 +113,7 @@ export function executeContextTool(tc) {
|
|
|
112
113
|
*/
|
|
113
114
|
function validatePath(filePath) {
|
|
114
115
|
const resolved = resolve(filePath);
|
|
115
|
-
|
|
116
|
-
return resolved;
|
|
117
|
-
}
|
|
118
|
-
return null;
|
|
116
|
+
return isWithinRoot(REPO_ROOT, resolved) ? resolved : null;
|
|
119
117
|
}
|
|
120
118
|
function executeReadFile(args) {
|
|
121
119
|
const filePath = args.path;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* True when `target` is the root itself or a descendant of it. Uses the
|
|
3
|
+
* platform separator (the old check hardcoded "/", which broke on Windows)
|
|
4
|
+
* and avoids the sibling-prefix trap (/repo vs /repo-evil). Both inputs are
|
|
5
|
+
* assumed already resolved to absolute paths.
|
|
6
|
+
*/
|
|
7
|
+
export declare function isWithinRoot(root: string, target: string): boolean;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import * as path from "node:path";
|
|
2
|
+
/**
|
|
3
|
+
* True when `target` is the root itself or a descendant of it. Uses the
|
|
4
|
+
* platform separator (the old check hardcoded "/", which broke on Windows)
|
|
5
|
+
* and avoids the sibling-prefix trap (/repo vs /repo-evil). Both inputs are
|
|
6
|
+
* assumed already resolved to absolute paths.
|
|
7
|
+
*/
|
|
8
|
+
export function isWithinRoot(root, target) {
|
|
9
|
+
if (target === root)
|
|
10
|
+
return true;
|
|
11
|
+
const rel = path.relative(root, target);
|
|
12
|
+
// Outside the root iff the relative path climbs out ("..") or is absolute
|
|
13
|
+
// (different drive on Windows).
|
|
14
|
+
return rel !== "" && !rel.startsWith("..") && !path.isAbsolute(rel);
|
|
15
|
+
}
|
|
@@ -61,7 +61,7 @@ export async function runArgueRound(args) {
|
|
|
61
61
|
// ─── Single-shot argue (no tools) ─────────────────────────────────────
|
|
62
62
|
async function argueSingleShot(args) {
|
|
63
63
|
const { critic, subject, format, draft, idPrefix, signal } = args;
|
|
64
|
-
const client = await createLLMClient(
|
|
64
|
+
const client = await createLLMClient(critic.llm, critic.clientDefaults);
|
|
65
65
|
const resp = await client.createMessage({
|
|
66
66
|
systemPrompt: format.argueSystem(format.format),
|
|
67
67
|
messages: [{ role: "user", content: format.argueUser(subject, draft) }],
|
|
@@ -78,7 +78,7 @@ async function argueSingleShot(args) {
|
|
|
78
78
|
// ─── Tool-use argue (with web search) ─────────────────────────────────
|
|
79
79
|
async function argueWithToolLoop(args) {
|
|
80
80
|
const { critic, subject, format, draft, idPrefix, maxToolRounds, signal } = args;
|
|
81
|
-
const client = await createLLMClient(
|
|
81
|
+
const client = await createLLMClient(critic.llm, critic.clientDefaults);
|
|
82
82
|
const messages = [
|
|
83
83
|
{ role: "user", content: format.argueUser(subject, draft) },
|
|
84
84
|
];
|
|
@@ -149,6 +149,12 @@ async function argueWithToolLoop(args) {
|
|
|
149
149
|
});
|
|
150
150
|
finalText = resp.text;
|
|
151
151
|
}
|
|
152
|
+
if (!finalText) {
|
|
153
|
+
// Both the tool loop and the force-conclude fallback produced no text
|
|
154
|
+
// (e.g. repeated max-token truncation). Log it — parseCritiquesResponse
|
|
155
|
+
// will just yield no critiques rather than silently parsing "".
|
|
156
|
+
logger.warn("arena.iterate.argue_empty_response", { critic: critic.name, toolRounds });
|
|
157
|
+
}
|
|
152
158
|
const critiques = parseCritiquesResponse(finalText, critic.name, idPrefix);
|
|
153
159
|
logger.info("arena.iterate.argue_toolloop", {
|
|
154
160
|
critic: critic.name,
|
|
@@ -7,7 +7,7 @@ import { parseReviseResponse } from "../parse.js";
|
|
|
7
7
|
export async function runRevise(args) {
|
|
8
8
|
const { subject, format, previous, critiques, author, minDraftLength, signal, onProgress } = args;
|
|
9
9
|
onProgress?.({ type: "revise_start", round: previous.version + 1, participant: author.name });
|
|
10
|
-
const client = await createLLMClient(
|
|
10
|
+
const client = await createLLMClient(author.llm, author.clientDefaults);
|
|
11
11
|
const prompt = format.revisePrompt(subject, previous, critiques, minDraftLength);
|
|
12
12
|
const resp = await client.createMessage({
|
|
13
13
|
systemPrompt: prompt,
|
|
@@ -11,7 +11,7 @@ export async function runTournamentCandidates(args) {
|
|
|
11
11
|
onProgress?.({ type: "v1_tournament_start", data: { count: participants.length } });
|
|
12
12
|
const tasks = participants.map(async (p, idx) => {
|
|
13
13
|
signal?.throwIfAborted();
|
|
14
|
-
const client = await createLLMClient(
|
|
14
|
+
const client = await createLLMClient(p.llm, p.clientDefaults);
|
|
15
15
|
const prompt = format.draftPrompt(subject, minDraftLength);
|
|
16
16
|
// Long-form generation needs a high output ceiling; the default 8k from
|
|
17
17
|
// LLMClientBase is too low for a substantive PRD or design doc and
|
|
@@ -66,7 +66,7 @@ export async function runTournamentCandidates(args) {
|
|
|
66
66
|
export async function mergeCandidatesToV1(args) {
|
|
67
67
|
const { subject, format, author, candidates, minDraftLength, signal, onProgress } = args;
|
|
68
68
|
onProgress?.({ type: "v1_merge_start", participant: author.name });
|
|
69
|
-
const client = await createLLMClient(
|
|
69
|
+
const client = await createLLMClient(author.llm, author.clientDefaults);
|
|
70
70
|
const prompt = format.mergePrompt(subject, candidates, minDraftLength);
|
|
71
71
|
const resp = await client.createMessage({
|
|
72
72
|
systemPrompt: prompt,
|
|
@@ -95,7 +95,7 @@ export async function mergeCandidatesToV1(args) {
|
|
|
95
95
|
/** Single-author v1 (tournament disabled) — author writes alone. */
|
|
96
96
|
export async function singleAuthorV1(args) {
|
|
97
97
|
const { subject, format, author, minDraftLength, signal } = args;
|
|
98
|
-
const client = await createLLMClient(
|
|
98
|
+
const client = await createLLMClient(author.llm, author.clientDefaults);
|
|
99
99
|
const prompt = format.draftPrompt(subject, minDraftLength);
|
|
100
100
|
const resp = await client.createMessage({
|
|
101
101
|
systemPrompt: prompt,
|
|
@@ -33,10 +33,7 @@ export async function runAdjudication(options) {
|
|
|
33
33
|
verified: verifiedClaims.length,
|
|
34
34
|
});
|
|
35
35
|
// Adjudicate contested claims via LLM
|
|
36
|
-
const client = await createLLMClient(
|
|
37
|
-
...concluder.llm,
|
|
38
|
-
enableStreaming: false,
|
|
39
|
-
});
|
|
36
|
+
const client = await createLLMClient(concluder.llm, concluder.clientDefaults);
|
|
40
37
|
for (const claim of contestedClaims) {
|
|
41
38
|
signal?.throwIfAborted();
|
|
42
39
|
const digest = buildDigest(ledger, {
|
|
@@ -18,10 +18,7 @@ import { isStrategyV2 } from "../types.js";
|
|
|
18
18
|
export async function buildConsensus(options) {
|
|
19
19
|
const { concluder, strategy, topic, reports, reviews, claimSummary, signal, onProgress } = options;
|
|
20
20
|
onProgress?.({ type: "consensus_start" });
|
|
21
|
-
const client = await createLLMClient(
|
|
22
|
-
...concluder.llm,
|
|
23
|
-
enableStreaming: false,
|
|
24
|
-
});
|
|
21
|
+
const client = await createLLMClient(concluder.llm, concluder.clientDefaults);
|
|
25
22
|
const systemPrompt = strategy.consensusSystemPrompt();
|
|
26
23
|
// Choose user prompt: claim-aware (V2) or standard (V1)
|
|
27
24
|
let userContent;
|
|
@@ -28,10 +28,7 @@ export async function runCrossReview(options) {
|
|
|
28
28
|
return [];
|
|
29
29
|
if (!myReport)
|
|
30
30
|
return [];
|
|
31
|
-
const client = await createLLMClient(
|
|
32
|
-
...p.llm,
|
|
33
|
-
enableStreaming: false,
|
|
34
|
-
});
|
|
31
|
+
const client = await createLLMClient(p.llm, p.clientDefaults);
|
|
35
32
|
const systemPrompt = strategy.crossReviewSystemPrompt(p.name);
|
|
36
33
|
const userContent = strategy.crossReviewUserPrompt(topic, myReport, otherReports);
|
|
37
34
|
let response = await client.createMessage({
|
|
@@ -110,10 +107,7 @@ export async function runVerificationReview(options) {
|
|
|
110
107
|
const myClaims = claimsToReview.filter((c) => c.owner !== p.name);
|
|
111
108
|
if (myClaims.length === 0)
|
|
112
109
|
return;
|
|
113
|
-
const client = await createLLMClient(
|
|
114
|
-
...p.llm,
|
|
115
|
-
enableStreaming: false,
|
|
116
|
-
});
|
|
110
|
+
const client = await createLLMClient(p.llm, p.clientDefaults);
|
|
117
111
|
const systemPrompt = strategy.crossReviewSystemPrompt(p.name);
|
|
118
112
|
if (v2) {
|
|
119
113
|
// V2 path: claim-aware verification review (or merge review for planning)
|
|
@@ -75,10 +75,7 @@ async function debateClaim(options) {
|
|
|
75
75
|
});
|
|
76
76
|
// Each debater produces a turn
|
|
77
77
|
const turnPromises = debaters.map(async (p) => {
|
|
78
|
-
const client = await createLLMClient(
|
|
79
|
-
...p.llm,
|
|
80
|
-
enableStreaming: false,
|
|
81
|
-
});
|
|
78
|
+
const client = await createLLMClient(p.llm, p.clientDefaults);
|
|
82
79
|
const systemPrompt = strategy.crossReviewSystemPrompt(p.name);
|
|
83
80
|
let userContent;
|
|
84
81
|
if (v2) {
|
|
@@ -27,10 +27,7 @@ export async function runParticipantResearchWithDossiers(options) {
|
|
|
27
27
|
const tools = enableContextTools ? (contextTools ?? CONTEXT_TOOLS) : undefined;
|
|
28
28
|
const tasks = participants.map(async (p) => {
|
|
29
29
|
onProgress?.({ type: "research_start", participant: p.name });
|
|
30
|
-
const client = await createLLMClient(
|
|
31
|
-
...p.llm,
|
|
32
|
-
enableStreaming: false,
|
|
33
|
-
});
|
|
30
|
+
const client = await createLLMClient(p.llm, p.clientDefaults);
|
|
34
31
|
const messages = [
|
|
35
32
|
{
|
|
36
33
|
role: "user",
|
|
@@ -31,10 +31,7 @@ export async function runDetailExpansion(options) {
|
|
|
31
31
|
onProgress?.({ type: "roadmap_expansion_start", phaseCount: toExpand.length });
|
|
32
32
|
logger.info("arena.detail_expansion_start", { phaseCount: toExpand.length });
|
|
33
33
|
const details = [];
|
|
34
|
-
const client = await createLLMClient(
|
|
35
|
-
...concluder.llm,
|
|
36
|
-
enableStreaming: false,
|
|
37
|
-
});
|
|
34
|
+
const client = await createLLMClient(concluder.llm, concluder.clientDefaults);
|
|
38
35
|
const systemPrompt = strategy.detailExpansionSystemPrompt();
|
|
39
36
|
// Expand each phase sequentially
|
|
40
37
|
for (const phase of toExpand) {
|
|
@@ -45,13 +42,18 @@ export async function runDetailExpansion(options) {
|
|
|
45
42
|
const userContent = strategy.detailExpansionUserPrompt(topic, phase, digest);
|
|
46
43
|
const messages = [{ role: "user", content: userContent }];
|
|
47
44
|
let finalText = "";
|
|
48
|
-
// Tool-use loop: allow LLM to verify file paths and interfaces
|
|
45
|
+
// Tool-use loop: allow LLM to verify file paths and interfaces.
|
|
46
|
+
// `maxRounds` bounds the number of tool rounds; after the last one we make
|
|
47
|
+
// one final call (without tools) so the model produces text FROM the tool
|
|
48
|
+
// results rather than the empty text it emitted alongside the tool calls.
|
|
49
49
|
const maxRounds = tools ? Math.min(MAX_EXPANSION_TOOL_ROUNDS, MAX_TOOL_ROUNDS) : 0;
|
|
50
50
|
for (let round = 0; round <= maxRounds; round++) {
|
|
51
|
+
// On the final round, stop offering tools so the model must answer.
|
|
52
|
+
const offerTools = round < maxRounds ? tools : undefined;
|
|
51
53
|
const response = await client.createMessage({
|
|
52
54
|
systemPrompt,
|
|
53
55
|
messages,
|
|
54
|
-
tools,
|
|
56
|
+
tools: offerTools,
|
|
55
57
|
signal,
|
|
56
58
|
});
|
|
57
59
|
const hasTools = response.toolCalls && response.toolCalls.length > 0;
|
|
@@ -90,10 +92,6 @@ export async function runDetailExpansion(options) {
|
|
|
90
92
|
});
|
|
91
93
|
}
|
|
92
94
|
messages.push({ role: "user", content: toolResultBlocks });
|
|
93
|
-
// Last round: force a text response
|
|
94
|
-
if (round === maxRounds) {
|
|
95
|
-
finalText = response.text;
|
|
96
|
-
}
|
|
97
95
|
}
|
|
98
96
|
// Retry if truncated
|
|
99
97
|
if (!finalText) {
|
package/dist/arena/planner.js
CHANGED
|
@@ -99,10 +99,13 @@ function safeReadDoc(filePath) {
|
|
|
99
99
|
return null;
|
|
100
100
|
}
|
|
101
101
|
}
|
|
102
|
-
function truncate(content) {
|
|
102
|
+
export function truncate(content) {
|
|
103
103
|
if (content.length <= MAX_DOC_CHARS)
|
|
104
104
|
return content;
|
|
105
105
|
const t = content.slice(0, MAX_DOC_CHARS);
|
|
106
106
|
const lastNl = t.lastIndexOf("\n");
|
|
107
|
-
|
|
107
|
+
// No newline in the window → lastNl is -1 and slice(0,-1) would drop just
|
|
108
|
+
// the last char; cut at MAX_DOC_CHARS instead.
|
|
109
|
+
const body = lastNl > 0 ? t.slice(0, lastNl) : t;
|
|
110
|
+
return body + `\n... (truncated, ${content.length} chars total)`;
|
|
108
111
|
}
|
|
@@ -192,12 +192,15 @@ function safeReadFile(filePath) {
|
|
|
192
192
|
return null;
|
|
193
193
|
}
|
|
194
194
|
}
|
|
195
|
-
function truncate(content) {
|
|
195
|
+
export function truncate(content) {
|
|
196
196
|
if (content.length <= MAX_FILE_CHARS)
|
|
197
197
|
return content;
|
|
198
198
|
const t = content.slice(0, MAX_FILE_CHARS);
|
|
199
199
|
const lastNl = t.lastIndexOf("\n");
|
|
200
|
-
|
|
200
|
+
// No newline in the window (e.g. a minified file) → lastNl is -1 and
|
|
201
|
+
// slice(0,-1) would drop just the last char; cut at MAX_FILE_CHARS instead.
|
|
202
|
+
const body = lastNl > 0 ? t.slice(0, lastNl) : t;
|
|
203
|
+
return body + `\n... (truncated, ${content.length} chars total)`;
|
|
201
204
|
}
|
|
202
205
|
async function safeGrep(pattern) {
|
|
203
206
|
try {
|
|
@@ -68,11 +68,44 @@ export function extractJSONArray(text) {
|
|
|
68
68
|
const fenced = text.match(/```(?:json)?\s*\n?([\s\S]*?)\n?```/);
|
|
69
69
|
if (fenced)
|
|
70
70
|
return fenced[1].trim();
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
71
|
+
// Return the FIRST balanced top-level array. A greedy /\[[\s\S]*\]/ spanned
|
|
72
|
+
// to the last ']', merging two arrays (or trailing prose) into one invalid
|
|
73
|
+
// blob. Scan for bracket balance, ignoring brackets inside strings.
|
|
74
|
+
const balanced = firstBalancedArray(text);
|
|
75
|
+
if (balanced)
|
|
76
|
+
return balanced;
|
|
74
77
|
return text;
|
|
75
78
|
}
|
|
79
|
+
function firstBalancedArray(text) {
|
|
80
|
+
const start = text.indexOf("[");
|
|
81
|
+
if (start === -1)
|
|
82
|
+
return undefined;
|
|
83
|
+
let depth = 0;
|
|
84
|
+
let inString = false;
|
|
85
|
+
let escaped = false;
|
|
86
|
+
for (let i = start; i < text.length; i++) {
|
|
87
|
+
const ch = text[i];
|
|
88
|
+
if (inString) {
|
|
89
|
+
if (escaped)
|
|
90
|
+
escaped = false;
|
|
91
|
+
else if (ch === "\\")
|
|
92
|
+
escaped = true;
|
|
93
|
+
else if (ch === '"')
|
|
94
|
+
inString = false;
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
if (ch === '"')
|
|
98
|
+
inString = true;
|
|
99
|
+
else if (ch === "[")
|
|
100
|
+
depth++;
|
|
101
|
+
else if (ch === "]") {
|
|
102
|
+
depth--;
|
|
103
|
+
if (depth === 0)
|
|
104
|
+
return text.slice(start, i + 1);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return undefined; // unbalanced
|
|
108
|
+
}
|
|
76
109
|
/** Format base context into a readable text block for LLM prompts */
|
|
77
110
|
export function formatBaseContext(ctx) {
|
|
78
111
|
const sections = [];
|
|
@@ -5,9 +5,14 @@
|
|
|
5
5
|
* consistent state transitions across phases.
|
|
6
6
|
*
|
|
7
7
|
* State machine:
|
|
8
|
-
* proposed → under_review
|
|
9
|
-
* under_review → verified | contested | rejected
|
|
8
|
+
* proposed → under_review | unresolved
|
|
9
|
+
* under_review → verified | contested | rejected | unresolved
|
|
10
10
|
* contested → under_review | unresolved | verified | rejected
|
|
11
|
+
*
|
|
12
|
+
* `unresolved` is reachable from proposed/under_review (not just contested):
|
|
13
|
+
* adjudication sweeps any claims left in those states when the round/budget
|
|
14
|
+
* is exhausted (adjudication.ts) — without these edges markUnresolved() would
|
|
15
|
+
* silently no-op and the claims would be dropped from the consensus summary.
|
|
11
16
|
*/
|
|
12
17
|
import type { ClaimRecord, ClaimStatus, ClaimChallenge } from "./types.js";
|
|
13
18
|
/**
|
|
@@ -5,14 +5,19 @@
|
|
|
5
5
|
* consistent state transitions across phases.
|
|
6
6
|
*
|
|
7
7
|
* State machine:
|
|
8
|
-
* proposed → under_review
|
|
9
|
-
* under_review → verified | contested | rejected
|
|
8
|
+
* proposed → under_review | unresolved
|
|
9
|
+
* under_review → verified | contested | rejected | unresolved
|
|
10
10
|
* contested → under_review | unresolved | verified | rejected
|
|
11
|
+
*
|
|
12
|
+
* `unresolved` is reachable from proposed/under_review (not just contested):
|
|
13
|
+
* adjudication sweeps any claims left in those states when the round/budget
|
|
14
|
+
* is exhausted (adjudication.ts) — without these edges markUnresolved() would
|
|
15
|
+
* silently no-op and the claims would be dropped from the consensus summary.
|
|
11
16
|
*/
|
|
12
17
|
/** Valid transitions from each status */
|
|
13
18
|
const VALID_TRANSITIONS = {
|
|
14
|
-
proposed: ["under_review"],
|
|
15
|
-
under_review: ["verified", "contested", "rejected"],
|
|
19
|
+
proposed: ["under_review", "unresolved"],
|
|
20
|
+
under_review: ["verified", "contested", "rejected", "unresolved"],
|
|
16
21
|
contested: ["under_review", "unresolved", "verified", "rejected"],
|
|
17
22
|
verified: [],
|
|
18
23
|
rejected: [],
|