@causyne/agent 1.10.21 → 1.11.0
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/native-skills/media/professional-document-publishing/SKILL.md +44 -0
- package/native-skills/media/screenplay-and-shotlist-forge/SKILL.md +42 -0
- package/package.json +1 -1
- package/src/acp/client.js +235 -0
- package/src/acp/client.ts +282 -0
- package/src/acp/peerProfiles.js +122 -0
- package/src/acp/peerProfiles.ts +128 -0
- package/src/acp/protocol.js +54 -0
- package/src/acp/protocol.ts +434 -0
- package/src/acp/server.js +609 -0
- package/src/acp/server.ts +676 -0
- package/src/agentLoop.d.ts +43 -0
- package/src/agentLoop.js +142 -8
- package/src/commands.js +203 -0
- package/src/compiledSkills.js +10 -0
- package/src/documentEngine/antiSlopAuditor.js +156 -0
- package/src/documentEngine/antiSlopAuditor.ts +221 -0
- package/src/documentEngine/documentForge.js +443 -0
- package/src/documentEngine/documentForge.ts +519 -0
- package/src/documentEngine/screenplayFormatter.js +336 -0
- package/src/documentEngine/screenplayFormatter.ts +407 -0
- package/src/entrypoint.js +13 -0
- package/src/execSafe.js +10 -13
- package/src/execSafe.ts +25 -0
- package/src/modelRouter.js +56 -123
- package/src/modelRouter.ts +134 -0
- package/src/observer/advisory.js +164 -0
- package/src/observer/advisory.ts +171 -0
- package/src/observer/coldObserver.js +462 -0
- package/src/observer/coldObserver.ts +540 -0
- package/src/observer/evidence.js +435 -0
- package/src/observer/evidence.ts +499 -0
- package/src/perception.js +303 -118
- package/src/perception.ts +387 -0
- package/src/procSignal.js +15 -16
- package/src/procSignal.ts +31 -0
- package/src/promptMentions.js +170 -0
- package/src/promptMentions.ts +212 -0
- package/src/sandbox.js +1 -1
- package/src/textDiff.js +117 -109
- package/src/textDiff.ts +169 -0
- package/src/toolPacks.js +5 -2
- package/src/tools/documentForge.js +215 -0
- package/src/tools/documentForge.ts +265 -0
- package/src/tools/peerAgent.js +102 -0
- package/src/tools/peerAgent.ts +116 -0
- package/src/tools/registry.js +3 -1
- package/src/tui/completion.js +97 -41
- package/src/tui/prompt.js +17 -17
- package/src/uncertainty.js +93 -205
- package/src/uncertainty.ts +255 -0
- package/src/utils/paths.js +15 -18
- package/src/utils/paths.ts +36 -0
- package/src/vfeTracker.js +122 -181
- package/src/vfeTracker.ts +242 -0
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
export interface AgentLoopEvent {
|
|
2
|
+
type: string;
|
|
3
|
+
[key: string]: unknown;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export interface AgentLoopEventSink {
|
|
7
|
+
readyState?: number;
|
|
8
|
+
send(payload: string): void;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface AgentLoopConfig {
|
|
12
|
+
autonomyMode?: 'A' | 'B' | 'C' | string;
|
|
13
|
+
maxStepsPerTask?: number;
|
|
14
|
+
tokenBudget?: Record<string, unknown>;
|
|
15
|
+
[key: string]: unknown;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface ApprovalDecision {
|
|
19
|
+
approved: boolean;
|
|
20
|
+
[key: string]: unknown;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export class AgentLoop {
|
|
24
|
+
constructor(eventSink: AgentLoopEventSink | null, taskId: string, userPrompt: string | null);
|
|
25
|
+
|
|
26
|
+
eventSink: AgentLoopEventSink | null;
|
|
27
|
+
taskId: string;
|
|
28
|
+
userPrompt: string | null;
|
|
29
|
+
workspaceRoot: string;
|
|
30
|
+
autonomyOverride: string | null;
|
|
31
|
+
config: AgentLoopConfig;
|
|
32
|
+
stepsCompleted: number;
|
|
33
|
+
|
|
34
|
+
run(): Promise<void>;
|
|
35
|
+
abort(): void;
|
|
36
|
+
rearmAbortController(): void;
|
|
37
|
+
setAutonomyMode(mode: 'A' | 'B' | 'C' | string): void;
|
|
38
|
+
emitEvent(event: AgentLoopEvent): void;
|
|
39
|
+
waitForApproval(toolCallId: string): Promise<boolean>;
|
|
40
|
+
handleApprovalResponse(toolCallId: string, decision: ApprovalDecision | boolean): void;
|
|
41
|
+
waitForDecision(request: Record<string, unknown>): Promise<unknown>;
|
|
42
|
+
updateTierLimits(): void;
|
|
43
|
+
}
|
package/src/agentLoop.js
CHANGED
|
@@ -22,6 +22,7 @@ import { routedConfig } from './modelRouter.js';
|
|
|
22
22
|
import { assessStep, isThrashing } from './uncertainty.js';
|
|
23
23
|
import { assessDecisionRequirement, isMutatingToolCall, requiresImplementationPlan } from './decisionPolicy.js';
|
|
24
24
|
import { VFETracker } from './vfeTracker.js';
|
|
25
|
+
import { coldObserver } from './observer/coldObserver.js';
|
|
25
26
|
import {
|
|
26
27
|
routePacks,
|
|
27
28
|
packForTool,
|
|
@@ -54,6 +55,7 @@ import os from 'os';
|
|
|
54
55
|
import { analyzeFiles, formatFindings, isScannableFile } from './slopDetector.js';
|
|
55
56
|
import { suggestMovements, formatMovementBrief } from './designPalettes.js';
|
|
56
57
|
import { createProvider } from './providers/factory.js';
|
|
58
|
+
import { resolveMentionedContext } from './promptMentions.js';
|
|
57
59
|
|
|
58
60
|
const SYSTEM_PROMPT = `You are Causyne Agent, a God-Tier Autonomous AI. You operate the user's computer through a local desktop harness. You are not just a coding assistant; you are a general-purpose superintelligence.
|
|
59
61
|
|
|
@@ -321,7 +323,9 @@ export class AgentLoop {
|
|
|
321
323
|
this.eventSink = eventSink;
|
|
322
324
|
this.taskId = taskId;
|
|
323
325
|
this.userPrompt = userPrompt;
|
|
324
|
-
|
|
326
|
+
this.workspaceRoot = process.cwd();
|
|
327
|
+
this.autonomyOverride = null;
|
|
328
|
+
|
|
325
329
|
this.config = loadConfig();
|
|
326
330
|
this.sandbox = new Sandbox();
|
|
327
331
|
|
|
@@ -378,6 +382,7 @@ export class AgentLoop {
|
|
|
378
382
|
// prompt-cache prefix intact when a pack loads mid-task.
|
|
379
383
|
this.unlockOrder = [];
|
|
380
384
|
this.workspaceContext = null;
|
|
385
|
+
this.activeSubproject = null;
|
|
381
386
|
this.taskShape = null;
|
|
382
387
|
// Budget for the billed Tier 2 Cortex condensation pass.
|
|
383
388
|
this._cortexPassesUsed = 0;
|
|
@@ -398,6 +403,7 @@ export class AgentLoop {
|
|
|
398
403
|
// P6: rendered surfaces this task wrote, for the anti-slop gate to read back.
|
|
399
404
|
this.webArtifacts = new Set();
|
|
400
405
|
this.slopGateFired = false;
|
|
406
|
+
this.coldObserverGuardFired = false;
|
|
401
407
|
|
|
402
408
|
this.gameCascadeState = { active: true, currentStage: 1, stageHistory: [], verificationPassed: false };
|
|
403
409
|
this.gameToolStats = { godot_act: 0, game_asset_fetch: 0, game_build_html5: 0, last_godot_action: null };
|
|
@@ -534,6 +540,15 @@ export class AgentLoop {
|
|
|
534
540
|
}
|
|
535
541
|
}
|
|
536
542
|
|
|
543
|
+
setAutonomyMode(mode) {
|
|
544
|
+
const normalized = String(mode || '').trim().toUpperCase();
|
|
545
|
+
if (!['A', 'B', 'C'].includes(normalized)) {
|
|
546
|
+
throw new Error('autonomy mode must be A, B, or C');
|
|
547
|
+
}
|
|
548
|
+
this.autonomyOverride = normalized;
|
|
549
|
+
this.config.autonomyMode = normalized;
|
|
550
|
+
}
|
|
551
|
+
|
|
537
552
|
/**
|
|
538
553
|
* A provider for UTILITY work — condensation, classification, filtering.
|
|
539
554
|
*
|
|
@@ -766,6 +781,7 @@ export class AgentLoop {
|
|
|
766
781
|
|
|
767
782
|
updateTierLimits() {
|
|
768
783
|
this.config = loadConfig();
|
|
784
|
+
if (this.autonomyOverride) this.config.autonomyMode = this.autonomyOverride;
|
|
769
785
|
if (typeof this.initializeProvider === 'function') {
|
|
770
786
|
this.llm = this.initializeProvider();
|
|
771
787
|
this._utilityLlm = null; // the route is derived from the model; re-derive it
|
|
@@ -817,6 +833,8 @@ export class AgentLoop {
|
|
|
817
833
|
// awareness instead of burning steps rediscovering it. The result used to be
|
|
818
834
|
// assigned to a local variable and thrown away.
|
|
819
835
|
auditLog.log({ event: 'task_start', taskId: this.taskId, request: this.userPrompt });
|
|
836
|
+
this.workspaceRoot = process.cwd();
|
|
837
|
+
coldObserver.beginTask(this.taskId);
|
|
820
838
|
this.emitEvent({ type: 'status', message: 'Analyzing workspace...' });
|
|
821
839
|
|
|
822
840
|
// P2: open the undo group for this task. This snapshots the CONVERSATION as
|
|
@@ -872,6 +890,7 @@ export class AgentLoop {
|
|
|
872
890
|
this.webGuardFired = false;
|
|
873
891
|
this.webArtifacts = new Set();
|
|
874
892
|
this.slopGateFired = false;
|
|
893
|
+
this.coldObserverGuardFired = false;
|
|
875
894
|
this.staticContext = null;
|
|
876
895
|
// Plan cache: the procedure THIS task follows, and the template (if any)
|
|
877
896
|
// that was offered to it. Both reset per task — a plan is a property of one
|
|
@@ -964,6 +983,36 @@ export class AgentLoop {
|
|
|
964
983
|
}
|
|
965
984
|
|
|
966
985
|
if (this.userPrompt) {
|
|
986
|
+
// 1b. RESOLVE @ MENTIONS FOR REFERENCED FILES, DIRECTORIES & SUBPROJECTS
|
|
987
|
+
try {
|
|
988
|
+
const subprojects = this.workspaceContext?.subprojects || [];
|
|
989
|
+
const mentionResult = await resolveMentionedContext(this.userPrompt, {
|
|
990
|
+
cwd: process.cwd(),
|
|
991
|
+
subprojects
|
|
992
|
+
});
|
|
993
|
+
|
|
994
|
+
if (mentionResult.detectedSubproject) {
|
|
995
|
+
this.activeSubproject = mentionResult.detectedSubproject;
|
|
996
|
+
this.notice(`Focused on subproject '${mentionResult.detectedSubproject}'`);
|
|
997
|
+
} else if (subprojects.length > 0) {
|
|
998
|
+
const lowerPrompt = this.userPrompt.toLowerCase();
|
|
999
|
+
for (const sp of subprojects) {
|
|
1000
|
+
if (lowerPrompt.includes(sp.toLowerCase())) {
|
|
1001
|
+
this.activeSubproject = sp;
|
|
1002
|
+
this.notice(`Focused on subproject '${sp}'`);
|
|
1003
|
+
break;
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
if (mentionResult.referencedContext) {
|
|
1009
|
+
this.userPrompt += mentionResult.referencedContext;
|
|
1010
|
+
this.notice(`Loaded ${mentionResult.mentions.length} referenced @ mention(s) into context`);
|
|
1011
|
+
}
|
|
1012
|
+
} catch (mentionErr) {
|
|
1013
|
+
this.debugLog(`mention resolution error: ${mentionErr.message}`);
|
|
1014
|
+
}
|
|
1015
|
+
|
|
967
1016
|
this.currentTaskPrompt = this.userPrompt; // Save for notification later
|
|
968
1017
|
|
|
969
1018
|
// V5: Multimodal Attachments Support
|
|
@@ -1210,12 +1259,15 @@ export class AgentLoop {
|
|
|
1210
1259
|
const ctx = this.workspaceContext;
|
|
1211
1260
|
const facts = [
|
|
1212
1261
|
ctx.cwd && `Working directory: ${ctx.cwd}`,
|
|
1262
|
+
this.activeSubproject && `Active project focus: ${this.activeSubproject}`,
|
|
1263
|
+
ctx.projectName && `Workspace project: ${ctx.projectName}`,
|
|
1213
1264
|
ctx.os && `OS: ${ctx.os}`,
|
|
1214
1265
|
ctx.framework && `Framework: ${ctx.framework}`,
|
|
1215
1266
|
ctx.projectType && `Project type: ${ctx.projectType}`,
|
|
1216
1267
|
ctx.packageManager && `Package manager: ${ctx.packageManager}`,
|
|
1217
1268
|
ctx.gitBranch && `Git branch: ${ctx.gitBranch}`,
|
|
1218
1269
|
ctx.gitStatus && `Git status: ${ctx.gitStatus}`,
|
|
1270
|
+
Array.isArray(ctx.subprojects) && ctx.subprojects.length > 0 && `Subprojects in workspace: ${ctx.subprojects.join(', ')}`,
|
|
1219
1271
|
Array.isArray(ctx.keyFiles) && ctx.keyFiles.length > 0 && `Key files: ${ctx.keyFiles.slice(0, 25).join(', ')}`
|
|
1220
1272
|
].filter(Boolean);
|
|
1221
1273
|
if (facts.length > 0) {
|
|
@@ -1640,6 +1692,32 @@ export class AgentLoop {
|
|
|
1640
1692
|
}
|
|
1641
1693
|
}
|
|
1642
1694
|
|
|
1695
|
+
// Cold Observer: Overconfidence & Loop Verification Check
|
|
1696
|
+
if (!this.coldObserverGuardFired) {
|
|
1697
|
+
try {
|
|
1698
|
+
const coldAudit = await coldObserver.auditTurnVerification({
|
|
1699
|
+
taskId: this.taskId,
|
|
1700
|
+
userPrompt: this.currentTaskPrompt || this.userPrompt || '',
|
|
1701
|
+
agentClaim: response.content || '',
|
|
1702
|
+
toolHistory: [],
|
|
1703
|
+
stepsCompleted: this.stepsCompleted
|
|
1704
|
+
});
|
|
1705
|
+
|
|
1706
|
+
if (coldAudit.overconfident) {
|
|
1707
|
+
this.coldObserverGuardFired = true;
|
|
1708
|
+
this.emitEvent({ type: 'status', message: `Cold Observer: ${coldAudit.discrepancy}` });
|
|
1709
|
+
this.messages.push({
|
|
1710
|
+
role: 'system',
|
|
1711
|
+
content: coldAudit.coldDirective
|
|
1712
|
+
});
|
|
1713
|
+
this.memoryManager.saveHistory(this.messages);
|
|
1714
|
+
continue;
|
|
1715
|
+
}
|
|
1716
|
+
} catch (_err) {
|
|
1717
|
+
// Cold observer fail-open
|
|
1718
|
+
}
|
|
1719
|
+
}
|
|
1720
|
+
|
|
1643
1721
|
this.emitEvent({ type: 'status', message: 'Task completed.' });
|
|
1644
1722
|
this.memoryManager.saveHistory(this.messages); // Save final state
|
|
1645
1723
|
|
|
@@ -1716,7 +1794,7 @@ export class AgentLoop {
|
|
|
1716
1794
|
continue;
|
|
1717
1795
|
}
|
|
1718
1796
|
|
|
1719
|
-
this.emitEvent({ type: 'step_start', tool: functionName, args: args });
|
|
1797
|
+
this.emitEvent({ type: 'step_start', tool: functionName, args: args, toolCallId: toolCall.id });
|
|
1720
1798
|
this.consecutiveCommands++;
|
|
1721
1799
|
|
|
1722
1800
|
if (functionName === 'screenshot' || functionName === 'vision_inspect_ui') {
|
|
@@ -1764,7 +1842,7 @@ export class AgentLoop {
|
|
|
1764
1842
|
|
|
1765
1843
|
if ((['fs_write'].includes(functionName) && isGameFileOrDir) || isEchoingToFile || isCustomScriptBlock) {
|
|
1766
1844
|
const interceptMsg = `[SOVEREIGN CASCADE VIOLATION: You attempted to skip from Stage ${this.gameCascadeState.currentStage} to Stage 7 without completing Stages 3, 4, 5, and 6 modularly. DO NOT write monolithic 20 KB custom scripts!]`;
|
|
1767
|
-
this.emitEvent({ type: 'step_error', tool: functionName, error: '
|
|
1845
|
+
this.emitEvent({ type: 'step_error', tool: functionName, error: 'Sovereign Game Intercept: Cascade violation detected.', toolCallId: toolCall.id });
|
|
1768
1846
|
this.messages.push({
|
|
1769
1847
|
role: 'tool',
|
|
1770
1848
|
tool_call_id: toolCall.id,
|
|
@@ -1802,7 +1880,7 @@ export class AgentLoop {
|
|
|
1802
1880
|
// Hard Circuit Breaker (Transcendence Context Consciousness)
|
|
1803
1881
|
if (this.consecutiveBlockedActions >= 4) {
|
|
1804
1882
|
const fatalMsg = `FATAL ERROR: Infinite Token Drain Detected. Agent repeatedly attempted blocked/forbidden actions 4 times in a row. Forcibly aborting task to save tokens.`;
|
|
1805
|
-
this.emitEvent({ type: 'step_error', tool: functionName, error: fatalMsg });
|
|
1883
|
+
this.emitEvent({ type: 'step_error', tool: functionName, error: fatalMsg, toolCallId: toolCall.id });
|
|
1806
1884
|
auditLog.log({ event: 'task_killed_circuit_breaker', taskId: this.taskId, reason: fatalMsg });
|
|
1807
1885
|
|
|
1808
1886
|
// Save memory of failure for RLTF
|
|
@@ -1817,7 +1895,11 @@ export class AgentLoop {
|
|
|
1817
1895
|
}
|
|
1818
1896
|
|
|
1819
1897
|
auditLog.log({ event: 'tool_blocked', taskId: this.taskId, tool: functionName, args, reason: validation.reason });
|
|
1820
|
-
this.
|
|
1898
|
+
coldObserver.recordToolOutcome(this.taskId, functionName, args, {
|
|
1899
|
+
success: false,
|
|
1900
|
+
error: validation.reason
|
|
1901
|
+
});
|
|
1902
|
+
this.emitEvent({ type: 'step_error', tool: functionName, error: validation.reason, toolCallId: toolCall.id });
|
|
1821
1903
|
|
|
1822
1904
|
// Feed error back to LLM with absolute command to stop repeating
|
|
1823
1905
|
this.messages.push({
|
|
@@ -1896,6 +1978,10 @@ export class AgentLoop {
|
|
|
1896
1978
|
event: 'hook_denied', taskId: this.taskId, hookEvent: 'PreToolUse',
|
|
1897
1979
|
tool: functionName, reason: preHook.reason || 'hook denied'
|
|
1898
1980
|
});
|
|
1981
|
+
coldObserver.recordToolOutcome(this.taskId, functionName, args, {
|
|
1982
|
+
success: false,
|
|
1983
|
+
error: `PreToolUse hook denied "${functionName}".`
|
|
1984
|
+
});
|
|
1899
1985
|
this.messages.push({
|
|
1900
1986
|
role: 'tool',
|
|
1901
1987
|
tool_call_id: toolCall.id,
|
|
@@ -1915,6 +2001,39 @@ export class AgentLoop {
|
|
|
1915
2001
|
// downloads ran unannounced.
|
|
1916
2002
|
const isHeavyAiDownload = functionName === 'manage_local_dependencies' &&
|
|
1917
2003
|
['pipInst', 'fetchBin', 'fetchMod'].includes(args.action);
|
|
2004
|
+
|
|
2005
|
+
// Cold Observer: Assess blast radius and mutation risk
|
|
2006
|
+
try {
|
|
2007
|
+
const coldMutation = await coldObserver.evaluateMutation(functionName, args, {
|
|
2008
|
+
workspaceRoot: this.workspaceRoot
|
|
2009
|
+
});
|
|
2010
|
+
if (coldMutation.veto) {
|
|
2011
|
+
this.emitEvent({
|
|
2012
|
+
type: 'status',
|
|
2013
|
+
message: `Cold Observer Veto: Action blocked (${coldMutation.reason})`
|
|
2014
|
+
});
|
|
2015
|
+
coldObserver.recordToolOutcome(this.taskId, functionName, args, {
|
|
2016
|
+
success: false,
|
|
2017
|
+
error: `[COLD OBSERVER VETO]: ${coldMutation.reason}`
|
|
2018
|
+
});
|
|
2019
|
+
this.messages.push({
|
|
2020
|
+
role: 'tool',
|
|
2021
|
+
tool_call_id: toolCall.id,
|
|
2022
|
+
name: functionName,
|
|
2023
|
+
content: JSON.stringify({
|
|
2024
|
+
success: false,
|
|
2025
|
+
error: `[COLD OBSERVER VETO]: ${coldMutation.reason}. Modify your approach to avoid project damage.`
|
|
2026
|
+
})
|
|
2027
|
+
});
|
|
2028
|
+
continue;
|
|
2029
|
+
}
|
|
2030
|
+
if (coldMutation.requiresConfirmation) {
|
|
2031
|
+
validation.risk = 'dangerous';
|
|
2032
|
+
}
|
|
2033
|
+
} catch (_err) {
|
|
2034
|
+
// Cold observer mutation assessment fail-open
|
|
2035
|
+
}
|
|
2036
|
+
|
|
1918
2037
|
// A standing approval from "Allow always" lifts the gate for this
|
|
1919
2038
|
// tool until the process restarts. It is checked HERE rather than
|
|
1920
2039
|
|
|
@@ -1944,6 +2063,10 @@ export class AgentLoop {
|
|
|
1944
2063
|
const approved = await this.waitForApproval(toolCall.id);
|
|
1945
2064
|
|
|
1946
2065
|
if (!approved) {
|
|
2066
|
+
coldObserver.recordToolOutcome(this.taskId, functionName, args, {
|
|
2067
|
+
success: false,
|
|
2068
|
+
error: 'User denied this action'
|
|
2069
|
+
});
|
|
1947
2070
|
this.messages.push({
|
|
1948
2071
|
role: 'tool',
|
|
1949
2072
|
tool_call_id: toolCall.id,
|
|
@@ -2031,7 +2154,8 @@ export class AgentLoop {
|
|
|
2031
2154
|
this.emitEvent({
|
|
2032
2155
|
type: 'step_error',
|
|
2033
2156
|
tool: functionName,
|
|
2034
|
-
error: `${errorType}: ${toolError.message} (retry ${currentRetries + 1}/${MAX_RETRIES} in ${backoffMs/1000}s)
|
|
2157
|
+
error: `${errorType}: ${toolError.message} (retry ${currentRetries + 1}/${MAX_RETRIES} in ${backoffMs/1000}s)`,
|
|
2158
|
+
toolCallId: toolCall.id
|
|
2035
2159
|
});
|
|
2036
2160
|
|
|
2037
2161
|
auditLog.log({
|
|
@@ -2066,7 +2190,12 @@ export class AgentLoop {
|
|
|
2066
2190
|
this.emitEvent({
|
|
2067
2191
|
type: 'step_error',
|
|
2068
2192
|
tool: functionName,
|
|
2069
|
-
error: `Failed after ${MAX_RETRIES} attempts: ${toolError.message}
|
|
2193
|
+
error: `Failed after ${MAX_RETRIES} attempts: ${toolError.message}`,
|
|
2194
|
+
toolCallId: toolCall.id
|
|
2195
|
+
});
|
|
2196
|
+
coldObserver.recordToolOutcome(this.taskId, functionName, args, {
|
|
2197
|
+
success: false,
|
|
2198
|
+
error: `PERMANENT FAILURE after ${MAX_RETRIES} retries: ${toolError.message}`
|
|
2070
2199
|
});
|
|
2071
2200
|
|
|
2072
2201
|
this.messages.push({
|
|
@@ -2104,6 +2233,11 @@ export class AgentLoop {
|
|
|
2104
2233
|
};
|
|
2105
2234
|
}
|
|
2106
2235
|
|
|
2236
|
+
// The journal is the observer's own record of what actually ran. It
|
|
2237
|
+
// is written only after post-hook review, so a formatter that rejects
|
|
2238
|
+
// a write is remembered as a failure rather than a success.
|
|
2239
|
+
coldObserver.recordToolOutcome(this.taskId, functionName, args, result);
|
|
2240
|
+
|
|
2107
2241
|
// PROGRESSIVE DISCLOSURE: honour unlock signals from tool_search.
|
|
2108
2242
|
// The control fields are stripped before the result reaches the LLM.
|
|
2109
2243
|
if (result && (result._unlock_tools || result._unlock_packs)) {
|
|
@@ -2121,7 +2255,7 @@ export class AgentLoop {
|
|
|
2121
2255
|
// Buffer output for disconnect resilience
|
|
2122
2256
|
await this.taskBuffer.appendOutput(this.taskId, { tool: functionName, result });
|
|
2123
2257
|
|
|
2124
|
-
this.emitEvent({ type: 'step_complete', tool: functionName, result: result._is_image_marker ? '[IMAGE DATA]' : result });
|
|
2258
|
+
this.emitEvent({ type: 'step_complete', tool: functionName, result: result._is_image_marker ? '[IMAGE DATA]' : result, toolCallId: toolCall.id });
|
|
2125
2259
|
this.stepsCompleted++;
|
|
2126
2260
|
|
|
2127
2261
|
// The procedure, as it actually ran. Tool name plus a whitelisted
|
package/src/commands.js
CHANGED
|
@@ -83,6 +83,56 @@ export const COMMANDS = [
|
|
|
83
83
|
}
|
|
84
84
|
},
|
|
85
85
|
|
|
86
|
+
{
|
|
87
|
+
name: 'cd',
|
|
88
|
+
aliases: ['cwd', 'workspace'],
|
|
89
|
+
scope: 'local',
|
|
90
|
+
usage: '/cd [directory]',
|
|
91
|
+
description: 'View or change active working directory and project focus',
|
|
92
|
+
complete: () => {
|
|
93
|
+
try {
|
|
94
|
+
const entries = fs.readdirSync(process.cwd(), { withFileTypes: true });
|
|
95
|
+
return entries
|
|
96
|
+
.filter(e => e.isDirectory() && !e.name.startsWith('.'))
|
|
97
|
+
.map(e => ({
|
|
98
|
+
value: e.name.includes(' ') ? `"${e.name}"` : e.name,
|
|
99
|
+
label: e.name + '/',
|
|
100
|
+
description: 'directory'
|
|
101
|
+
}));
|
|
102
|
+
} catch (e) {
|
|
103
|
+
return [];
|
|
104
|
+
}
|
|
105
|
+
},
|
|
106
|
+
run: async (argv, ctx) => {
|
|
107
|
+
const target = argv.join(' ').trim().replace(/^["']|["']$/g, '');
|
|
108
|
+
if (!target) {
|
|
109
|
+
return ok(`Current working directory: ${process.cwd()}`);
|
|
110
|
+
}
|
|
111
|
+
const resolved = path.resolve(process.cwd(), target.replace(/^~\//, os.homedir() + '/'));
|
|
112
|
+
if (!fs.existsSync(resolved)) {
|
|
113
|
+
return fail(`Directory not found: ${resolved}`);
|
|
114
|
+
}
|
|
115
|
+
const stat = fs.statSync(resolved);
|
|
116
|
+
if (!stat.isDirectory()) {
|
|
117
|
+
return fail(`Not a directory: ${resolved}`);
|
|
118
|
+
}
|
|
119
|
+
try {
|
|
120
|
+
process.chdir(resolved);
|
|
121
|
+
} catch (err) {
|
|
122
|
+
return fail(`Cannot switch to ${resolved}: ${err.message}`);
|
|
123
|
+
}
|
|
124
|
+
if (ctx.prompt && typeof ctx.prompt.setWorkingRoot === 'function') {
|
|
125
|
+
ctx.prompt.setWorkingRoot(resolved);
|
|
126
|
+
}
|
|
127
|
+
if (ctx.loop && ctx.loop.perception) {
|
|
128
|
+
try {
|
|
129
|
+
ctx.loop.workspaceContext = await ctx.loop.perception.buildContext(resolved);
|
|
130
|
+
} catch (e) {}
|
|
131
|
+
}
|
|
132
|
+
return ok(`Working directory changed to: ${resolved}`);
|
|
133
|
+
}
|
|
134
|
+
},
|
|
135
|
+
|
|
86
136
|
{
|
|
87
137
|
name: 'skill',
|
|
88
138
|
scope: 'local',
|
|
@@ -878,6 +928,159 @@ export const COMMANDS = [
|
|
|
878
928
|
run: (argv, ctx) => ok(buildStatusText({ loop: ctx.loop || null }))
|
|
879
929
|
},
|
|
880
930
|
|
|
931
|
+
{
|
|
932
|
+
name: 'observer',
|
|
933
|
+
scope: 'local',
|
|
934
|
+
usage: '/observer [status|toggle|mode <deterministic|advisory>|endpoint <url|none>|model <name>|strictness <relaxed|standard|strict>|test|audit|reset]',
|
|
935
|
+
description: 'Inspect or configure the Cold Observer',
|
|
936
|
+
dialogTitle: 'Cold Observer',
|
|
937
|
+
run: async (argv) => {
|
|
938
|
+
const { coldObserver } = await import('./observer/coldObserver.js');
|
|
939
|
+
const sub = (argv[0] || 'status').toLowerCase();
|
|
940
|
+
|
|
941
|
+
if (sub === 'toggle') {
|
|
942
|
+
const current = coldObserver.getConfig();
|
|
943
|
+
coldObserver.updateConfig({ enabled: !current.enabled });
|
|
944
|
+
return ok(`Cold Observer is now ${!current.enabled ? 'ENABLED' : 'DISABLED'}.`);
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
if (sub === 'mode') {
|
|
948
|
+
const targetMode = String(argv[1] || '').toLowerCase();
|
|
949
|
+
if (!['deterministic', 'advisory', 'local', 'hybrid'].includes(targetMode)) {
|
|
950
|
+
return fail('Invalid mode. Choose: deterministic or advisory.');
|
|
951
|
+
}
|
|
952
|
+
coldObserver.updateConfig({ mode: targetMode });
|
|
953
|
+
return ok(`Cold Observer mode set to "${coldObserver.getConfig().mode}".`);
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
if (sub === 'endpoint') {
|
|
957
|
+
const target = String(argv[1] || '').trim();
|
|
958
|
+
try {
|
|
959
|
+
coldObserver.updateConfig({ endpoint: target === 'none' ? '' : target });
|
|
960
|
+
} catch (error) {
|
|
961
|
+
return fail(error instanceof Error ? error.message : 'Invalid endpoint.');
|
|
962
|
+
}
|
|
963
|
+
const endpoint = coldObserver.getConfig().endpoint;
|
|
964
|
+
return ok(endpoint ? `Advisory endpoint set to ${endpoint}.` : 'Advisory endpoint cleared.');
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
if (sub === 'model') {
|
|
968
|
+
const target = String(argv[1] || '').trim();
|
|
969
|
+
coldObserver.updateConfig({ model: target === 'none' ? '' : target });
|
|
970
|
+
const model = coldObserver.getConfig().model;
|
|
971
|
+
return ok(model ? `Advisory model set to ${model}.` : 'Advisory model cleared.');
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
if (sub === 'strictness') {
|
|
975
|
+
const target = String(argv[1] || '').toLowerCase();
|
|
976
|
+
if (!['relaxed', 'standard', 'strict', 'moderate', 'paranoid'].includes(target)) {
|
|
977
|
+
return fail('Invalid strictness. Choose: relaxed, standard, or strict.');
|
|
978
|
+
}
|
|
979
|
+
coldObserver.updateConfig({ strictness: target });
|
|
980
|
+
return ok(`Cold Observer strictness set to "${coldObserver.getConfig().strictness}".`);
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
if (sub === 'test') {
|
|
984
|
+
const health = await coldObserver.healthCheck();
|
|
985
|
+
if (!health.endpoint) {
|
|
986
|
+
return ok('No advisory endpoint configured. The deterministic observer needs no endpoint and remains active.');
|
|
987
|
+
}
|
|
988
|
+
return ok([
|
|
989
|
+
`Endpoint: ${health.endpoint}`,
|
|
990
|
+
`Model: ${health.model || '(unset)'}`,
|
|
991
|
+
`Healthy: ${health.healthy ? 'yes' : 'no'} (${health.latencyMs}ms)`,
|
|
992
|
+
`Detail: ${health.detail}`
|
|
993
|
+
].join('\n'));
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
if (sub === 'reset') {
|
|
997
|
+
coldObserver.updateConfig({
|
|
998
|
+
enabled: true,
|
|
999
|
+
mode: 'deterministic',
|
|
1000
|
+
endpoint: '',
|
|
1001
|
+
model: '',
|
|
1002
|
+
timeoutMs: 800,
|
|
1003
|
+
strictness: 'standard'
|
|
1004
|
+
});
|
|
1005
|
+
return ok('Cold Observer configuration reset to defaults.');
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
if (sub === 'audit') {
|
|
1009
|
+
const audits = coldObserver.getAuditHistory();
|
|
1010
|
+
if (audits.length === 0) {
|
|
1011
|
+
return ok('No Cold Observer audit records yet.');
|
|
1012
|
+
}
|
|
1013
|
+
const lines = audits.slice(-10).map((a) => {
|
|
1014
|
+
const time = new Date(a.timestamp).toLocaleTimeString();
|
|
1015
|
+
const tag = a.type === 'mutation' ? `[mutation ${a.tool || ''}]` : '[verification]';
|
|
1016
|
+
const score = typeof a.riskScore === 'number' ? `(risk ${(a.riskScore * 100).toFixed(0)}%)` : '';
|
|
1017
|
+
const veto = a.veto ? 'VETO' : 'pass';
|
|
1018
|
+
return `${time} ${tag} ${veto} ${score} ${a.details}`;
|
|
1019
|
+
});
|
|
1020
|
+
return ok(`Recent Cold Observer audits (last 10):\n\n${lines.join('\n')}`);
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
const cfg = coldObserver.getConfig();
|
|
1024
|
+
const audits = coldObserver.getAuditHistory();
|
|
1025
|
+
const vetoes = audits.filter((a) => a.veto).length;
|
|
1026
|
+
return ok([
|
|
1027
|
+
'Cold Observer',
|
|
1028
|
+
'──────────────────────────────────────────────',
|
|
1029
|
+
`Status: ${cfg.enabled ? 'Active (mutations and completion claims)' : 'Disabled'}`,
|
|
1030
|
+
`Mode: ${cfg.mode.toUpperCase()}`,
|
|
1031
|
+
`Strictness: ${cfg.strictness}`,
|
|
1032
|
+
`Advisory: ${cfg.endpoint ? `${cfg.model || '(model unset)'} at ${cfg.endpoint}` : 'not configured (deterministic only)'}`,
|
|
1033
|
+
`Audit events: ${audits.length} recorded (${vetoes} vetoes enforced)`,
|
|
1034
|
+
'',
|
|
1035
|
+
'Subcommands:',
|
|
1036
|
+
' /observer toggle Enable or disable protection',
|
|
1037
|
+
' /observer mode <deterministic|advisory> Choose the review mode',
|
|
1038
|
+
' /observer endpoint <url|none> Set a loopback advisory endpoint',
|
|
1039
|
+
' /observer model <name> Set the advisory model name',
|
|
1040
|
+
' /observer strictness <relaxed|standard|strict>',
|
|
1041
|
+
' /observer test Probe the advisory endpoint',
|
|
1042
|
+
' /observer audit Inspect recent audits',
|
|
1043
|
+
' /observer reset Restore defaults'
|
|
1044
|
+
].join('\n'));
|
|
1045
|
+
}
|
|
1046
|
+
},
|
|
1047
|
+
|
|
1048
|
+
{
|
|
1049
|
+
name: 'acp',
|
|
1050
|
+
scope: 'local',
|
|
1051
|
+
usage: '/acp [status|peers]',
|
|
1052
|
+
description: 'Agent Client Protocol (ACP) server and peer delegation status',
|
|
1053
|
+
dialogTitle: 'Agent Client Protocol',
|
|
1054
|
+
run: async (argv) => {
|
|
1055
|
+
const sub = (argv[0] || 'status').toLowerCase();
|
|
1056
|
+
if (sub === 'peer' || sub === 'peers') {
|
|
1057
|
+
const { loadPeerProfiles, peerProfilesPath } = await import('./acp/peerProfiles.js');
|
|
1058
|
+
const profiles = loadPeerProfiles();
|
|
1059
|
+
return ok([
|
|
1060
|
+
'Peer delegation (ACP)',
|
|
1061
|
+
'──────────────────────────────────────────────',
|
|
1062
|
+
`Profiles file: ${peerProfilesPath()}`,
|
|
1063
|
+
`Configured: ${profiles.length === 0 ? 'none' : profiles.map((p) => `${p.id} (${p.policy})`).join(', ')}`,
|
|
1064
|
+
'',
|
|
1065
|
+
'The model selects a profile id and writes a prompt. Executable, arguments,',
|
|
1066
|
+
'working directory, and environment always come from the profile file.'
|
|
1067
|
+
].join('\n'));
|
|
1068
|
+
}
|
|
1069
|
+
return ok([
|
|
1070
|
+
'Agent Client Protocol (ACP) - Causyne Agent',
|
|
1071
|
+
'──────────────────────────────────────────────',
|
|
1072
|
+
'Version: ACP v1 (JSON-RPC 2.0 over stdio)',
|
|
1073
|
+
'Server command: causyne acp',
|
|
1074
|
+
'Wire: newline-delimited JSON-RPC',
|
|
1075
|
+
'Sessions: session/new, session/prompt, session/cancel, session/update',
|
|
1076
|
+
'Optional: session/list, session/close, session/set_mode',
|
|
1077
|
+
'Workspace: one workspace per server process',
|
|
1078
|
+
'',
|
|
1079
|
+
'Run "causyne acp" to connect this agent to any ACP client.'
|
|
1080
|
+
].join('\n'));
|
|
1081
|
+
}
|
|
1082
|
+
},
|
|
1083
|
+
|
|
881
1084
|
{
|
|
882
1085
|
name: 'exit',
|
|
883
1086
|
aliases: ['quit'],
|
package/src/compiledSkills.js
CHANGED
|
@@ -329,6 +329,16 @@ export const COMPILED_SKILLS = {
|
|
|
329
329
|
"description": "Automate photo editing, graphic composition, text typography, and image format exports using native Photo Studio automation. Use when a task requires batch image edits, graphic layout creation, resizing, color adjustment, or PSD/PNG/JPG/WEBP asset generation.",
|
|
330
330
|
"content": "---\nname: photo-studio-automation\ndescription: Automate photo editing, graphic composition, text typography, and image format exports using native Photo Studio automation. Use when a task requires batch image edits, graphic layout creation, resizing, color adjustment, or PSD/PNG/JPG/WEBP asset generation.\n---\n\n# Photo Studio Automation\n\nDirect script execution beats screenshot clicking every time: it is exact, sub-second, costs zero vision tokens, and never misclicks because a window moved. Use the native Creative Studio bridge via `creative_apps` with `action: \"studio_edit\"` and `host: \"photo-studio\"`.\n\n## Workflow\n\n1. Verify environment and inspect installed creative hosts.\n - Run `creative_apps` with `action: \"status\"`.\n - Confirm Photo Studio availability before assembling jobs.\n - **Done when:** host availability is confirmed.\n\n2. Establish workspace paths.\n - All inputs and outputs must reside strictly within the active workspace.\n - The output file must not already exist (the runtime enforces collision prevention).\n - Supported output extensions: `.psd`, `.png`, `.jpg`, `.jpeg`, `.webp`.\n - **Done when:** source asset and clean target output paths are defined.\n\n3. Plan layer hierarchy and typography before applying edits.\n - If starting from scratch, omit `input` to let the engine create a canvas (1920x1080 standard).\n - If transforming existing art, provide `input` path.\n - Layer modifications should specify explicit `name`, `opacity`, `visible`, and `blendMode`.\n - Typography actions should define `text`, `x`, `y`, `size`, and font family.\n - **Done when:** sequence of edit actions is organized logically from background to foreground.\n\n4. Apply color adjustments and canvas sizing.\n - Use `resize` or `crop` early in the action pipeline to establish composition geometry.\n - Use `adjust_color` with `brightness` (-100..100) and `contrast` (-100..100) on target layers for grading.\n - If delivering flat web assets (e.g., PNG or WEBP), include `flatten` at the end of the action chain.\n - **Done when:** geometric and tonal parameters match design requirements.\n\n5. Execute batch and verify output.\n - Invoke `creative_apps` with `action: \"studio_edit\"`, `host: \"photo-studio\"`, `output`, and `actions`.\n - The runtime verifies execution receipt and confirms nonempty file generation.\n - **Done when:** output file exists, has nonzero byte count, and matches requested specifications.\n\n## Failure Modes Worth Naming\n\n- **Overwriting existing file:** The engine halts if output exists. Specify a distinct or versioned filename.\n- **Outside workspace:** All paths must resolve inside the workspace root. Do not reference temporary directories or home root.\n- **Missing host:** If the host is not installed, the tool gracefully reports installation status. Propose alternative image processing or check system paths.\n- **Out-of-range layer index:** Layer indices start at 0. Ensure index matches existing layer count.\n\n## Verification\n\n- `action: \"studio_edit\"` returns `success: true`.\n- Output file exists on disk within workspace.\n- File size is non-zero.\n- Format matches target extension (.psd, .png, .jpg, or .webp).\n"
|
|
331
331
|
},
|
|
332
|
+
"professional-document-publishing": {
|
|
333
|
+
"name": "professional-document-publishing",
|
|
334
|
+
"description": "Generate publication-grade PDF documents, grant proposals, financial sheets, and technical reports using HTML/CSS Paged Media and Headless Chrome. Enforces visual quality inspection, under 2MB compliance, and mathematical integrity.",
|
|
335
|
+
"content": "---\nname: professional-document-publishing\ndescription: Generate publication-grade PDF documents, grant proposals, financial sheets, and technical reports using HTML/CSS Paged Media and Headless Chrome. Enforces visual quality inspection, under 2MB compliance, and mathematical integrity.\n---\n\n# Professional Document Publishing & Harness\n\nFrontier LLMs and efficient models produce equally stunning, publication-ready documents when guided by this harness methodology.\n\nNever assemble print layouts through raw Python scripts. Always author in semantic HTML5 plus CSS Paged Media, compile via Headless Chrome, and visually inspect the rendered output.\n\n## Workflow\n\n1. Prepare structured data and schemas.\n - Keep numeric budget and scheduling data in structured JSON format.\n - Run math validation with `document_forge` (`action: \"audit_budget\"`).\n - **Done when:** all numbers are validated against the single source of truth.\n\n2. Compile document markup and styles.\n - Author page layouts in HTML using standard CSS `@page` rules (`size: A4 portrait; margin: 20mm 18mm 18mm 18mm;`).\n - Use `@page cover { margin: 0; }` for full-bleed cover banners without white borders.\n - Force `-webkit-print-color-adjust: exact; print-color-adjust: exact;` so backgrounds and gradients render faithfully.\n - Prevent mid-table row cuts with `tr { page-break-inside: avoid; }`.\n - Prevent orphan headers with `h1, h2, h3 { page-break-after: avoid; }`.\n - Compile via `document_forge` (`action: \"compile_document\"`).\n - **Done when:** HTML markup and CSS paged styles are written to disk.\n\n3. Render to vector PDF via Headless Chrome.\n - Invoke `document_forge` (`action: \"render_pdf\"`, `sourceHtmlPath`, `outputPdfPath`).\n - **Done when:** vector PDF file exists on disk with non-zero size.\n\n4. Inspect pages visually and audit compliance.\n - Extract page images via `document_forge` (`action: \"inspect_pages\"`).\n - Verify page fill density: flag and repair any pages with less than 25% text coverage.\n - Confirm PDF file size is below 2,048 KB for portal compliance.\n - Audit text content for long em-dashes and AI clichés with `document_forge` (`action: \"audit_text\"`).\n - **Done when:** all pages pass visual density check and size constraints.\n\n## Verification\n\n- `document_forge` returns `success: true`.\n- Output PDF file size is non-zero and under 2,048 KB.\n- Extracted page images show no clipped text or orphaned headers.\n- Mathematical totals match across all sections.\n"
|
|
336
|
+
},
|
|
337
|
+
"screenplay-and-shotlist-forge": {
|
|
338
|
+
"name": "screenplay-and-shotlist-forge",
|
|
339
|
+
"description": "Format professional Hollywood screenplays in Courier 12pt, create director production shot lists, and build voiceover timing cue sheets with Words-Per-Minute pacing analysis. Use for movie scripts, video storytelling, documentary shotlists, and audio dubbing timelines.",
|
|
340
|
+
"content": "---\nname: screenplay-and-shotlist-forge\ndescription: Format professional Hollywood screenplays in Courier 12pt, create director production shot lists, and build voiceover timing cue sheets with Words-Per-Minute pacing analysis. Use for movie scripts, video storytelling, documentary shotlists, and audio dubbing timelines.\n---\n\n# Screenplay & Production Shot List Forge\n\nHigh-end cinematic storytelling and video production require standardized industry formatting so that directors, cinematographers, and editors can immediately execute on set and in the edit suite.\n\n## Specifications\n\n- **Screenplay Typography:** Courier 12pt monospaced font, exactly 6 lines per inch vertical rhythm.\n- **Screenplay Margins:** Left margin 1.5 inches; right, top, and bottom margins 1.0 inch.\n- **Pacing Standard:** Voiceover narration moves at 130 to 155 Words-Per-Minute for natural documentary cadence.\n\n## Workflow\n\n1. Format screenplay elements and layout.\n - Author scenes using `document_forge` (`action: \"format_screenplay\"`, `screenplaySpec`).\n - Format scene headings in uppercase, character cues indented 2.2 inches, and dialogue width constrained to 3.5 inches.\n - **Done when:** script HTML and CSS with Courier 12pt specifications are generated.\n\n2. Build director's production shot list.\n - Generate production shot lists with `document_forge` (`action: \"format_shotlist\"`, `shotList`).\n - Assign camera framing (EWS, WS, MS, CU, ECU), lenses, movement, and audio cues.\n - **Done when:** structured shot list grid is compiled with cumulative duration estimates.\n\n3. Analyze voiceover timing and narration pacing.\n - Audit narration pacing with `document_forge` (`action: \"analyze_vo\"`, `voBlocks`).\n - Ensure word density stays within 130 to 155 WPM for optimal documentary pacing.\n - **Done when:** pacing status is confirmed as optimal across all audio blocks.\n\n4. Render and verify publication files.\n - Render scripts or shot lists to vector PDF via `document_forge` (`action: \"render_pdf\"`).\n - **Done when:** output PDF files exist and are verified visually.\n\n## Verification\n\n- `document_forge` returns `success: true`.\n- Screenplay HTML contains Courier 12pt typography and 1.5-inch left margin.\n- Shot list table summarizes total shots and total runtime.\n- Voiceover analysis confirms pacing within target WPM limits.\n"
|
|
341
|
+
},
|
|
332
342
|
"video-timeline-assembly": {
|
|
333
343
|
"name": "video-timeline-assembly",
|
|
334
344
|
"description": "Assemble video timelines, import media clips to sequences, place markers, make precision edits, and export project files using native Video Studio automation. Use when a task requires automated video sequence building, cutting, multicam timeline layout, or PRPROJ/XML project generation.",
|