@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,122 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { causyneHome } from '../utils/paths.js';
|
|
4
|
+
const PROFILE_ID = /^[a-z0-9][a-z0-9._-]{0,63}$/;
|
|
5
|
+
const ENV_NAME = /^[A-Za-z_][A-Za-z0-9_]{0,127}$/;
|
|
6
|
+
const MAX_PEERS = 20;
|
|
7
|
+
const MAX_ARGS = 32;
|
|
8
|
+
const MAX_ARG_CHARS = 500;
|
|
9
|
+
const MAX_ENV_ENTRIES = 32;
|
|
10
|
+
const MAX_ENV_VALUE_CHARS = 2000;
|
|
11
|
+
export function peerProfilesPath() {
|
|
12
|
+
return path.join(causyneHome(), 'peers.json');
|
|
13
|
+
}
|
|
14
|
+
function asStringArray(value, limit, maxChars) {
|
|
15
|
+
if (!Array.isArray(value))
|
|
16
|
+
return null;
|
|
17
|
+
if (value.length > limit)
|
|
18
|
+
return null;
|
|
19
|
+
const result = [];
|
|
20
|
+
for (const item of value) {
|
|
21
|
+
if (typeof item !== 'string')
|
|
22
|
+
return null;
|
|
23
|
+
result.push(item.slice(0, maxChars));
|
|
24
|
+
}
|
|
25
|
+
return result;
|
|
26
|
+
}
|
|
27
|
+
function asEnv(value) {
|
|
28
|
+
if (value === null || typeof value !== 'object' || Array.isArray(value))
|
|
29
|
+
return {};
|
|
30
|
+
const result = {};
|
|
31
|
+
let count = 0;
|
|
32
|
+
for (const [name, raw] of Object.entries(value)) {
|
|
33
|
+
if (count >= MAX_ENV_ENTRIES)
|
|
34
|
+
break;
|
|
35
|
+
if (!ENV_NAME.test(name))
|
|
36
|
+
continue;
|
|
37
|
+
if (typeof raw !== 'string')
|
|
38
|
+
continue;
|
|
39
|
+
result[name] = raw.slice(0, MAX_ENV_VALUE_CHARS);
|
|
40
|
+
count += 1;
|
|
41
|
+
}
|
|
42
|
+
return result;
|
|
43
|
+
}
|
|
44
|
+
function parseProfile(raw) {
|
|
45
|
+
if (raw === null || typeof raw !== 'object' || Array.isArray(raw))
|
|
46
|
+
return null;
|
|
47
|
+
const record = raw;
|
|
48
|
+
const id = typeof record.id === 'string' ? record.id.trim() : '';
|
|
49
|
+
if (!PROFILE_ID.test(id))
|
|
50
|
+
return null;
|
|
51
|
+
const command = typeof record.command === 'string' ? record.command.trim() : '';
|
|
52
|
+
if (!command || command.length > 500)
|
|
53
|
+
return null;
|
|
54
|
+
const args = asStringArray(record.args ?? [], MAX_ARGS, MAX_ARG_CHARS);
|
|
55
|
+
if (args === null)
|
|
56
|
+
return null;
|
|
57
|
+
let cwd = null;
|
|
58
|
+
if (typeof record.cwd === 'string' && record.cwd.trim()) {
|
|
59
|
+
const resolved = path.resolve(record.cwd.trim());
|
|
60
|
+
try {
|
|
61
|
+
if (!fs.statSync(resolved).isDirectory())
|
|
62
|
+
return null;
|
|
63
|
+
cwd = resolved;
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
const policy = record.policy === 'workspace' ? 'workspace' : 'read-only';
|
|
70
|
+
const timeoutSeconds = typeof record.timeoutSeconds === 'number' && Number.isFinite(record.timeoutSeconds)
|
|
71
|
+
? Math.max(5, Math.min(1800, Math.round(record.timeoutSeconds)))
|
|
72
|
+
: 120;
|
|
73
|
+
const label = typeof record.label === 'string' && record.label.trim()
|
|
74
|
+
? record.label.trim().slice(0, 100)
|
|
75
|
+
: id;
|
|
76
|
+
return { id, label, command, args, cwd, policy, timeoutSeconds, env: asEnv(record.env) };
|
|
77
|
+
}
|
|
78
|
+
export function loadPeerProfiles() {
|
|
79
|
+
let raw;
|
|
80
|
+
try {
|
|
81
|
+
raw = JSON.parse(fs.readFileSync(peerProfilesPath(), 'utf8'));
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
return [];
|
|
85
|
+
}
|
|
86
|
+
let entries;
|
|
87
|
+
if (Array.isArray(raw)) {
|
|
88
|
+
entries = raw;
|
|
89
|
+
}
|
|
90
|
+
else if (raw !== null && typeof raw === 'object' && Array.isArray(raw.peers)) {
|
|
91
|
+
entries = raw.peers;
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
return [];
|
|
95
|
+
}
|
|
96
|
+
const profiles = [];
|
|
97
|
+
const seen = new Set();
|
|
98
|
+
for (const entry of entries) {
|
|
99
|
+
if (profiles.length >= MAX_PEERS)
|
|
100
|
+
break;
|
|
101
|
+
const profile = parseProfile(entry);
|
|
102
|
+
if (!profile || seen.has(profile.id))
|
|
103
|
+
continue;
|
|
104
|
+
seen.add(profile.id);
|
|
105
|
+
profiles.push(profile);
|
|
106
|
+
}
|
|
107
|
+
return profiles;
|
|
108
|
+
}
|
|
109
|
+
export function getPeerProfile(id) {
|
|
110
|
+
return loadPeerProfiles().find((profile) => profile.id === id) ?? null;
|
|
111
|
+
}
|
|
112
|
+
export function peerEnvironment(profile) {
|
|
113
|
+
const env = {};
|
|
114
|
+
for (const name of ['PATH', 'HOME', 'TMPDIR', 'TMP', 'TEMP', 'LANG', 'LC_ALL', 'SYSTEMROOT']) {
|
|
115
|
+
const value = process.env[name];
|
|
116
|
+
if (typeof value === 'string')
|
|
117
|
+
env[name] = value;
|
|
118
|
+
}
|
|
119
|
+
for (const [name, value] of Object.entries(profile.env))
|
|
120
|
+
env[name] = value;
|
|
121
|
+
return env;
|
|
122
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { causyneHome } from '../utils/paths.js';
|
|
4
|
+
|
|
5
|
+
export type PeerPolicy = 'read-only' | 'workspace';
|
|
6
|
+
|
|
7
|
+
export interface PeerProfile {
|
|
8
|
+
id: string;
|
|
9
|
+
label: string;
|
|
10
|
+
command: string;
|
|
11
|
+
args: string[];
|
|
12
|
+
cwd: string | null;
|
|
13
|
+
policy: PeerPolicy;
|
|
14
|
+
timeoutSeconds: number;
|
|
15
|
+
env: Record<string, string>;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface PeerProfilesFile {
|
|
19
|
+
schemaVersion: number;
|
|
20
|
+
peers: PeerProfile[];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const PROFILE_ID = /^[a-z0-9][a-z0-9._-]{0,63}$/;
|
|
24
|
+
const ENV_NAME = /^[A-Za-z_][A-Za-z0-9_]{0,127}$/;
|
|
25
|
+
const MAX_PEERS = 20;
|
|
26
|
+
const MAX_ARGS = 32;
|
|
27
|
+
const MAX_ARG_CHARS = 500;
|
|
28
|
+
const MAX_ENV_ENTRIES = 32;
|
|
29
|
+
const MAX_ENV_VALUE_CHARS = 2000;
|
|
30
|
+
|
|
31
|
+
export function peerProfilesPath(): string {
|
|
32
|
+
return path.join(causyneHome(), 'peers.json');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function asStringArray(value: unknown, limit: number, maxChars: number): string[] | null {
|
|
36
|
+
if (!Array.isArray(value)) return null;
|
|
37
|
+
if (value.length > limit) return null;
|
|
38
|
+
const result: string[] = [];
|
|
39
|
+
for (const item of value) {
|
|
40
|
+
if (typeof item !== 'string') return null;
|
|
41
|
+
result.push(item.slice(0, maxChars));
|
|
42
|
+
}
|
|
43
|
+
return result;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function asEnv(value: unknown): Record<string, string> {
|
|
47
|
+
if (value === null || typeof value !== 'object' || Array.isArray(value)) return {};
|
|
48
|
+
const result: Record<string, string> = {};
|
|
49
|
+
let count = 0;
|
|
50
|
+
for (const [name, raw] of Object.entries(value as Record<string, unknown>)) {
|
|
51
|
+
if (count >= MAX_ENV_ENTRIES) break;
|
|
52
|
+
if (!ENV_NAME.test(name)) continue;
|
|
53
|
+
if (typeof raw !== 'string') continue;
|
|
54
|
+
result[name] = raw.slice(0, MAX_ENV_VALUE_CHARS);
|
|
55
|
+
count += 1;
|
|
56
|
+
}
|
|
57
|
+
return result;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function parseProfile(raw: unknown): PeerProfile | null {
|
|
61
|
+
if (raw === null || typeof raw !== 'object' || Array.isArray(raw)) return null;
|
|
62
|
+
const record = raw as Record<string, unknown>;
|
|
63
|
+
const id = typeof record.id === 'string' ? record.id.trim() : '';
|
|
64
|
+
if (!PROFILE_ID.test(id)) return null;
|
|
65
|
+
const command = typeof record.command === 'string' ? record.command.trim() : '';
|
|
66
|
+
if (!command || command.length > 500) return null;
|
|
67
|
+
const args = asStringArray(record.args ?? [], MAX_ARGS, MAX_ARG_CHARS);
|
|
68
|
+
if (args === null) return null;
|
|
69
|
+
let cwd: string | null = null;
|
|
70
|
+
if (typeof record.cwd === 'string' && record.cwd.trim()) {
|
|
71
|
+
const resolved = path.resolve(record.cwd.trim());
|
|
72
|
+
try {
|
|
73
|
+
if (!fs.statSync(resolved).isDirectory()) return null;
|
|
74
|
+
cwd = resolved;
|
|
75
|
+
} catch {
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
const policy: PeerPolicy = record.policy === 'workspace' ? 'workspace' : 'read-only';
|
|
80
|
+
const timeoutSeconds = typeof record.timeoutSeconds === 'number' && Number.isFinite(record.timeoutSeconds)
|
|
81
|
+
? Math.max(5, Math.min(1800, Math.round(record.timeoutSeconds)))
|
|
82
|
+
: 120;
|
|
83
|
+
const label = typeof record.label === 'string' && record.label.trim()
|
|
84
|
+
? record.label.trim().slice(0, 100)
|
|
85
|
+
: id;
|
|
86
|
+
return { id, label, command, args, cwd, policy, timeoutSeconds, env: asEnv(record.env) };
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function loadPeerProfiles(): PeerProfile[] {
|
|
90
|
+
let raw: unknown;
|
|
91
|
+
try {
|
|
92
|
+
raw = JSON.parse(fs.readFileSync(peerProfilesPath(), 'utf8'));
|
|
93
|
+
} catch {
|
|
94
|
+
return [];
|
|
95
|
+
}
|
|
96
|
+
let entries: unknown;
|
|
97
|
+
if (Array.isArray(raw)) {
|
|
98
|
+
entries = raw;
|
|
99
|
+
} else if (raw !== null && typeof raw === 'object' && Array.isArray((raw as Record<string, unknown>).peers)) {
|
|
100
|
+
entries = (raw as Record<string, unknown>).peers;
|
|
101
|
+
} else {
|
|
102
|
+
return [];
|
|
103
|
+
}
|
|
104
|
+
const profiles: PeerProfile[] = [];
|
|
105
|
+
const seen = new Set<string>();
|
|
106
|
+
for (const entry of entries as unknown[]) {
|
|
107
|
+
if (profiles.length >= MAX_PEERS) break;
|
|
108
|
+
const profile = parseProfile(entry);
|
|
109
|
+
if (!profile || seen.has(profile.id)) continue;
|
|
110
|
+
seen.add(profile.id);
|
|
111
|
+
profiles.push(profile);
|
|
112
|
+
}
|
|
113
|
+
return profiles;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export function getPeerProfile(id: string): PeerProfile | null {
|
|
117
|
+
return loadPeerProfiles().find((profile) => profile.id === id) ?? null;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export function peerEnvironment(profile: PeerProfile): NodeJS.ProcessEnv {
|
|
121
|
+
const env: NodeJS.ProcessEnv = {};
|
|
122
|
+
for (const name of ['PATH', 'HOME', 'TMPDIR', 'TMP', 'TEMP', 'LANG', 'LC_ALL', 'SYSTEMROOT']) {
|
|
123
|
+
const value = process.env[name];
|
|
124
|
+
if (typeof value === 'string') env[name] = value;
|
|
125
|
+
}
|
|
126
|
+
for (const [name, value] of Object.entries(profile.env)) env[name] = value;
|
|
127
|
+
return env;
|
|
128
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent Client Protocol (ACP) types for Causyne Agent.
|
|
3
|
+
*
|
|
4
|
+
* JSON-RPC 2.0 over stdio with newline-delimited framing. The shapes here
|
|
5
|
+
* follow the published ACP schema; the agent advertises only the capabilities
|
|
6
|
+
* it actually implements.
|
|
7
|
+
*/
|
|
8
|
+
export const ACP_PROTOCOL_VERSION = 1;
|
|
9
|
+
export const ACP_METHODS = {
|
|
10
|
+
INITIALIZE: 'initialize',
|
|
11
|
+
NEW_SESSION: 'session/new',
|
|
12
|
+
PROMPT: 'session/prompt',
|
|
13
|
+
CANCEL: 'session/cancel',
|
|
14
|
+
UPDATE: 'session/update',
|
|
15
|
+
LOAD: 'session/load',
|
|
16
|
+
LIST: 'session/list',
|
|
17
|
+
CLOSE: 'session/close',
|
|
18
|
+
SET_MODE: 'session/set_mode',
|
|
19
|
+
REQUEST_PERMISSION: 'session/request_permission',
|
|
20
|
+
READ_TEXT_FILE: 'fs/read_text_file',
|
|
21
|
+
WRITE_TEXT_FILE: 'fs/write_text_file'
|
|
22
|
+
};
|
|
23
|
+
export const ACP_ERROR_CODES = {
|
|
24
|
+
PARSE_ERROR: -32700,
|
|
25
|
+
INVALID_REQUEST: -32600,
|
|
26
|
+
METHOD_NOT_FOUND: -32601,
|
|
27
|
+
INVALID_PARAMS: -32602,
|
|
28
|
+
INTERNAL_ERROR: -32603,
|
|
29
|
+
AUTH_REQUIRED: -32000,
|
|
30
|
+
RESOURCE_NOT_FOUND: -32002,
|
|
31
|
+
REQUEST_CANCELLED: -32800
|
|
32
|
+
};
|
|
33
|
+
export function isJsonRpcRequest(value) {
|
|
34
|
+
if (value === null || typeof value !== 'object' || Array.isArray(value))
|
|
35
|
+
return false;
|
|
36
|
+
const record = value;
|
|
37
|
+
return record.jsonrpc === '2.0'
|
|
38
|
+
&& (typeof record.id === 'string' || typeof record.id === 'number')
|
|
39
|
+
&& typeof record.method === 'string';
|
|
40
|
+
}
|
|
41
|
+
export function isJsonRpcResponse(value) {
|
|
42
|
+
if (value === null || typeof value !== 'object' || Array.isArray(value))
|
|
43
|
+
return false;
|
|
44
|
+
const record = value;
|
|
45
|
+
return record.jsonrpc === '2.0'
|
|
46
|
+
&& (typeof record.id === 'string' || typeof record.id === 'number' || record.id === null)
|
|
47
|
+
&& ('result' in record || 'error' in record);
|
|
48
|
+
}
|
|
49
|
+
export function isJsonRpcNotification(value) {
|
|
50
|
+
if (value === null || typeof value !== 'object' || Array.isArray(value))
|
|
51
|
+
return false;
|
|
52
|
+
const record = value;
|
|
53
|
+
return record.jsonrpc === '2.0' && typeof record.method === 'string' && !('id' in record);
|
|
54
|
+
}
|