subconscious-cli 6.0.0 → 6.1.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/README.md +135 -2
- package/bin/agent-data.js +242 -0
- package/bin/agent-help.js +106 -0
- package/bin/agent-install.js +354 -0
- package/bin/agent-launch.js +199 -0
- package/bin/agent-spawn.js +137 -0
- package/bin/agents.js +284 -840
- package/bin/auth.js +89 -33
- package/bin/branding.js +3 -1
- package/bin/claude-picker.js +143 -0
- package/bin/cli.js +87 -20
- package/bin/clipboard-image.js +24 -5
- package/bin/colors.js +1 -1
- package/bin/feedback.js +71 -21
- package/bin/harness-manifest.js +286 -0
- package/bin/headless-args.js +35 -0
- package/bin/launch-values.js +25 -0
- package/bin/model-capabilities.js +12 -8
- package/bin/models.js +35 -19
- package/bin/native/subc-tui-darwin-amd64 +0 -0
- package/bin/native/subc-tui-darwin-arm64 +0 -0
- package/bin/native/subc-tui-linux-amd64 +0 -0
- package/bin/native/subc-tui-linux-arm64 +0 -0
- package/bin/native/subc-tui-windows-amd64.exe +0 -0
- package/bin/native/subc-tui-windows-arm64.exe +0 -0
- package/bin/opencode-provider.js +48 -5
- package/bin/profile-settings.js +129 -0
- package/bin/profiles.js +136 -449
- package/bin/runbook/README.md +66 -23
- package/bin/runbook/claude-code/agent.json +300 -0
- package/bin/runbook/claude-code/install.sh +9 -252
- package/bin/runbook/codex/agent.json +461 -0
- package/bin/runbook/codex/hooks-lib.sh +17 -4
- package/bin/runbook/codex/install.sh +0 -4
- package/bin/runbook/codex/run.sh +16 -235
- package/bin/runbook/copilot/agent.json +185 -0
- package/bin/runbook/copilot/install.sh +45 -63
- package/bin/runbook/cursor/agent.json +137 -0
- package/bin/runbook/cursor/install.sh +8 -35
- package/bin/runbook/deepseek-harness/agent.json +163 -0
- package/bin/runbook/deepseek-harness/run.sh +20 -59
- package/bin/runbook/image-window/index.ts +14 -0
- package/bin/runbook/image-window/window.d.ts +19 -0
- package/bin/runbook/image-window/window.js +140 -0
- package/bin/runbook/lib.sh +41 -0
- package/bin/runbook/opencode/agent.json +158 -0
- package/bin/runbook/opencode/install.sh +9 -183
- package/bin/runbook/opencode/opencode-plugin.d.ts +16 -0
- package/bin/runbook/opencode/subconscious-image-window.ts +24 -0
- package/bin/runbook/pi/agent.json +216 -0
- package/bin/runbook/pi/install.sh +18 -44
- package/bin/runbook/pi/run.sh +6 -18
- package/bin/runbook/pi/subconscious-compaction.ts +71 -49
- package/bin/runbook/shared.json +58 -0
- package/bin/runbook/subconscious-code/agent.json +156 -0
- package/bin/sessions.js +173 -58
- package/bin/tui.js +104 -32
- package/bin/update-check.js +54 -21
- package/bin/upgrade.js +67 -43
- package/bin/usage.js +47 -24
- package/bin/windows/agents.js +141 -37
- package/bin/windows/common.js +36 -25
- package/bin/windows/install.js +116 -34
- package/bin/windows/launch.js +260 -77
- package/bin/windows/pi-compaction.ts +68 -13
- package/bin/windows/process.js +210 -57
- package/bin/windows/setup.js +251 -56
- package/package.json +9 -4
- package/bin/registry.generated.json +0 -406
- package/bin/runbook/claude-code/run.sh +0 -254
- package/bin/runbook/model-capabilities.generated.sh +0 -9
- package/bin/runbook/opencode/provider.sh +0 -93
- package/bin/runbook/opencode/run.sh +0 -99
- package/bin/runbook/opencode/subconscious-compaction.ts +0 -100
- package/bin/runbook/subconscious-code/run.sh +0 -21
- package/bin/windows/claude.js +0 -71
package/bin/windows/launch.js
CHANGED
|
@@ -2,95 +2,278 @@
|
|
|
2
2
|
import fs from 'node:fs/promises';
|
|
3
3
|
import os from 'node:os';
|
|
4
4
|
import path from 'node:path';
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
5
|
+
import { agentById, agentKeyName } from '../agent-data.js';
|
|
6
|
+
import {
|
|
7
|
+
launchPlan,
|
|
8
|
+
resolveAgentEnv,
|
|
9
|
+
resolveInputs,
|
|
10
|
+
takeFlags,
|
|
11
|
+
} from '../agent-launch.js';
|
|
12
|
+
import { claudePickerSettings } from '../claude-picker.js';
|
|
13
|
+
import { modelIds, positiveInteger } from '../launch-values.js';
|
|
7
14
|
import { modelSupportsVision } from '../model-capabilities.js';
|
|
8
|
-
import { defaults,
|
|
15
|
+
import { defaults, origin, value, writeJson } from './common.js';
|
|
9
16
|
|
|
10
|
-
|
|
17
|
+
const PLANNED = new Set(['claude-code', 'subconscious-code', 'pi', 'opencode']);
|
|
18
|
+
|
|
19
|
+
// Windows trims a configured gateway to its origin and gives every agent but
|
|
20
|
+
// Claude the gateway and key as SUBCONSCIOUS_*. Claude has always been given
|
|
21
|
+
// the gateway as configured.
|
|
22
|
+
function windowsEnv(id, env) {
|
|
23
|
+
const model = value(env, 'MODEL', defaults.model);
|
|
24
|
+
const gateway = value(env, 'GATEWAY_URL', defaults.baseUrl);
|
|
25
|
+
if (id === 'claude-code')
|
|
26
|
+
return { ...env, MODEL: model, GATEWAY_URL: gateway };
|
|
27
|
+
const keyName = agentKeyName(agentById(id));
|
|
11
28
|
return {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
29
|
+
...env,
|
|
30
|
+
MODEL: model,
|
|
31
|
+
GATEWAY_URL: origin(gateway),
|
|
32
|
+
SUBCONSCIOUS_API_KEY: value(env, keyName, value(env, 'API_KEY')),
|
|
33
|
+
SUBCONSCIOUS_GATEWAY_URL: origin(gateway),
|
|
15
34
|
};
|
|
16
35
|
}
|
|
17
36
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
}
|
|
26
|
-
if (
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
const output = positiveInteger(value(env, 'OPENCODE_OUTPUT_LIMIT', '65536'), 'OPENCODE_OUTPUT_LIMIT');
|
|
30
|
-
childEnv.OPENCODE_CONFIG_CONTENT = JSON.stringify(
|
|
31
|
-
buildOpenCodeConfig({ baseUrl: base, model, modelIds: modelIds(env), context, output }),
|
|
37
|
+
function claudeLaunch(agent, argv, env) {
|
|
38
|
+
const settings =
|
|
39
|
+
value(env, 'SUBC_CLAUDE_SETTINGS') ||
|
|
40
|
+
JSON.stringify(claudePickerSettings([env.MODEL], env.MODEL));
|
|
41
|
+
const plan = launchPlan(agent, {
|
|
42
|
+
env: { ...env, SUBC_CLAUDE_SETTINGS: settings },
|
|
43
|
+
args: argv,
|
|
44
|
+
});
|
|
45
|
+
if (!plan.env.ANTHROPIC_BASE_URL || !plan.env.ANTHROPIC_AUTH_TOKEN) {
|
|
46
|
+
throw new Error(
|
|
47
|
+
'GATEWAY_URL and API_KEY are required to launch Claude Code',
|
|
32
48
|
);
|
|
33
|
-
return { command: 'opencode', args: parseOptions(argv, {}).rest, env: childEnv };
|
|
34
49
|
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
50
|
+
positiveInteger(plan.env.CLAUDE_CODE_AUTO_COMPACT_WINDOW, 'compact-window');
|
|
51
|
+
positiveInteger(
|
|
52
|
+
plan.env.CLAUDE_CODE_MAX_CONTEXT_TOKENS,
|
|
53
|
+
'max-context-tokens',
|
|
54
|
+
);
|
|
55
|
+
return plan;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function plannedLaunch(agent, argv, env) {
|
|
59
|
+
const plan =
|
|
60
|
+
agent.id === 'claude-code'
|
|
61
|
+
? claudeLaunch(agent, argv, env)
|
|
62
|
+
: launchPlan(agent, { env, args: argv });
|
|
63
|
+
return { command: plan.argv[0], args: plan.argv.slice(1), env: plan.env };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function codexCatalog(env, context, maxContext, compact) {
|
|
67
|
+
return {
|
|
68
|
+
models: modelIds(env).map((id) => ({
|
|
69
|
+
slug: id,
|
|
70
|
+
display_name: id,
|
|
71
|
+
description: `Subconscious model ${id}`,
|
|
72
|
+
context_window: context,
|
|
73
|
+
max_context_window: maxContext,
|
|
74
|
+
auto_compact_token_limit: compact,
|
|
75
|
+
effective_context_window_percent: 95,
|
|
76
|
+
supported_reasoning_levels: [],
|
|
77
|
+
shell_type: 'shell_command',
|
|
78
|
+
visibility: 'list',
|
|
79
|
+
supported_in_api: true,
|
|
80
|
+
priority: 0,
|
|
81
|
+
service_tiers: [
|
|
82
|
+
{
|
|
83
|
+
id: 'priority',
|
|
84
|
+
name: 'Priority',
|
|
85
|
+
description:
|
|
86
|
+
'Route requests through the configured priority service tier',
|
|
87
|
+
},
|
|
88
|
+
],
|
|
89
|
+
availability_nux: null,
|
|
90
|
+
upgrade: null,
|
|
91
|
+
base_instructions: 'You are Codex, a coding agent.',
|
|
92
|
+
supports_reasoning_summaries: false,
|
|
93
|
+
support_verbosity: false,
|
|
94
|
+
default_verbosity: null,
|
|
95
|
+
apply_patch_tool_type: 'freeform',
|
|
96
|
+
truncation_policy: { mode: 'tokens', limit: 10000 },
|
|
97
|
+
supports_parallel_tool_calls: true,
|
|
98
|
+
experimental_supported_tools: [],
|
|
99
|
+
// Codex assumes ['text', 'image'] when the field is missing.
|
|
100
|
+
input_modalities: modelSupportsVision(id) ? ['text', 'image'] : ['text'],
|
|
101
|
+
})),
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// --subagents is Windows-only: it runs a pinned older Codex instead.
|
|
106
|
+
function splitSubagents(argv) {
|
|
107
|
+
const boundary = argv.indexOf('--');
|
|
108
|
+
const head = boundary < 0 ? argv : argv.slice(0, boundary);
|
|
109
|
+
const tail = boundary < 0 ? [] : argv.slice(boundary);
|
|
110
|
+
for (const arg of head)
|
|
111
|
+
if (arg.startsWith('--subagents='))
|
|
112
|
+
throw new Error('--subagents does not take a value');
|
|
113
|
+
return {
|
|
114
|
+
subagents: head.includes('--subagents'),
|
|
115
|
+
words: [...head.filter((arg) => arg !== '--subagents'), ...tail],
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function codexConfig(env, file) {
|
|
120
|
+
const config = {
|
|
121
|
+
model: env.MODEL,
|
|
122
|
+
model_provider: 'subconscious',
|
|
123
|
+
model_catalog_json: file,
|
|
124
|
+
model_reasoning_effort: env.CODEX_REASONING_EFFORT,
|
|
125
|
+
web_search: 'disabled',
|
|
126
|
+
'model_providers.subconscious.name': 'Subconscious',
|
|
127
|
+
'model_providers.subconscious.base_url': `${env.GATEWAY_URL}/v1`,
|
|
128
|
+
'model_providers.subconscious.wire_api': 'responses',
|
|
129
|
+
'model_providers.subconscious.env_key': 'SUBCONSCIOUS_API_KEY',
|
|
130
|
+
'model_providers.subconscious.stream_idle_timeout_ms': Number(
|
|
131
|
+
env.CODEX_STREAM_IDLE_TIMEOUT_MS,
|
|
132
|
+
),
|
|
133
|
+
};
|
|
134
|
+
if (env.CODEX_EXTERNAL_TOOLS !== 'true') {
|
|
135
|
+
Object.assign(config, {
|
|
136
|
+
'features.apps': false,
|
|
137
|
+
'features.plugins': false,
|
|
138
|
+
'apps._default.enabled': false,
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
return config;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
async function codexLaunch(agent, argv, env, tempRoot) {
|
|
145
|
+
const { subagents, words } = splitSubagents(argv);
|
|
146
|
+
// Windows Codex sets no subagent effort, and uses the subagent cap only
|
|
147
|
+
// with --subagents; otherwise those flags still reach Codex unchanged.
|
|
148
|
+
const { values, rest } = takeFlags(agent, words, {
|
|
149
|
+
skip: [
|
|
150
|
+
'CODEX_SUBAGENT_REASONING_EFFORT',
|
|
151
|
+
...(subagents ? [] : ['MAX_CONCURRENT_SUBAGENTS']),
|
|
152
|
+
],
|
|
153
|
+
});
|
|
154
|
+
const resolved = resolveAgentEnv(agent, resolveInputs(agent, env, values));
|
|
155
|
+
const context = positiveInteger(
|
|
156
|
+
resolved.CODEX_CONTEXT_WINDOW,
|
|
157
|
+
'context-window',
|
|
158
|
+
);
|
|
159
|
+
const maxContext = positiveInteger(
|
|
160
|
+
resolved.CODEX_MAX_CONTEXT_WINDOW,
|
|
161
|
+
'max-context-window',
|
|
162
|
+
);
|
|
163
|
+
const compact = positiveInteger(
|
|
164
|
+
resolved.CODEX_AUTO_COMPACT_TOKEN_LIMIT,
|
|
165
|
+
'auto-compact-token-limit',
|
|
166
|
+
);
|
|
167
|
+
const catalog = codexCatalog(resolved, context, maxContext, compact);
|
|
168
|
+
// Validate all flags before creating a temporary directory.
|
|
169
|
+
positiveInteger(resolved.CODEX_STREAM_IDLE_TIMEOUT_MS, 'stream-idle-timeout');
|
|
170
|
+
const threads = positiveInteger(
|
|
171
|
+
resolved.MAX_CONCURRENT_SUBAGENTS,
|
|
172
|
+
'MAX_CONCURRENT_SUBAGENTS',
|
|
173
|
+
);
|
|
174
|
+
const dir = await fs.mkdtemp(path.join(tempRoot, 'subc-codex-'));
|
|
175
|
+
const cleanup = () => fs.rm(dir, { recursive: true, force: true });
|
|
176
|
+
try {
|
|
177
|
+
const file = path.join(dir, 'models.json');
|
|
178
|
+
await writeJson(file, catalog);
|
|
179
|
+
const config = codexConfig(resolved, file);
|
|
180
|
+
if (subagents)
|
|
181
|
+
Object.assign(config, {
|
|
182
|
+
'features.multi_agent': true,
|
|
183
|
+
'agents.max_threads': threads,
|
|
184
|
+
'agents.max_depth': 1,
|
|
185
|
+
'agents.interrupt_message': true,
|
|
186
|
+
});
|
|
187
|
+
const args = Object.entries(config).flatMap(([k, v]) => [
|
|
188
|
+
'-c',
|
|
189
|
+
`${k}=${JSON.stringify(v)}`,
|
|
190
|
+
]);
|
|
191
|
+
return {
|
|
192
|
+
command: subagents ? 'npx' : 'codex',
|
|
193
|
+
args: [
|
|
194
|
+
...(subagents ? ['-y', '@openai/codex@0.132.0'] : []),
|
|
195
|
+
...args,
|
|
196
|
+
...rest,
|
|
197
|
+
],
|
|
198
|
+
env: resolved,
|
|
199
|
+
cleanup,
|
|
200
|
+
};
|
|
201
|
+
} catch (error) {
|
|
202
|
+
await cleanup();
|
|
203
|
+
throw error;
|
|
74
204
|
}
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function dshOverlay(env, context, maxTokens) {
|
|
208
|
+
const models = modelIds(env)
|
|
209
|
+
.map(
|
|
210
|
+
(id) =>
|
|
211
|
+
` - id: '${id}'\n name: '${id}'\n${modelSupportsVision(id) ? ' input: [text, image]\n' : ''} contextWindow: ${context}\n maxTokens: ${maxTokens}`,
|
|
212
|
+
)
|
|
213
|
+
.join('\n');
|
|
214
|
+
return `- id: llm-pi-ai\n config:\n providers:\n subconscious:\n apiKeyEnv: SUBCONSCIOUS_API_KEY\n displayName: Subconscious Gateway\n api: openai-completions\n baseURL: !!js process.env.SUBCONSCIOUS_DSH_BASE_URL\n headers:\n x-subconscious-client: deepseek-harness\n compat:\n supportsDeveloperRole: false\n maxTokensField: max_tokens\n defaultContextWindow: ${context}\n defaultMaxTokens: ${maxTokens}\n models:\n${models}\n- id: agent-default-model\n config:\n provider: subconscious\n model: '${env.MODEL}'\n`;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
async function dshLaunch(agent, argv, env, tempRoot) {
|
|
218
|
+
const headless = argv[0] === 'headless';
|
|
219
|
+
const plan = launchPlan(agent, {
|
|
220
|
+
env,
|
|
221
|
+
args: headless ? argv.slice(2) : argv,
|
|
222
|
+
prompt: headless ? argv[1] : undefined,
|
|
223
|
+
});
|
|
224
|
+
const context = positiveInteger(
|
|
225
|
+
plan.env.DEEPSEEK_HARNESS_CONTEXT_WINDOW,
|
|
226
|
+
'DEEPSEEK_HARNESS_CONTEXT_WINDOW',
|
|
227
|
+
);
|
|
228
|
+
const maxTokens = positiveInteger(
|
|
229
|
+
plan.env.DEEPSEEK_HARNESS_MAX_TOKENS,
|
|
230
|
+
'DEEPSEEK_HARNESS_MAX_TOKENS',
|
|
231
|
+
);
|
|
232
|
+
const text = dshOverlay(plan.env, context, maxTokens);
|
|
233
|
+
const dir = await fs.mkdtemp(path.join(tempRoot, 'subc-dsh-'));
|
|
234
|
+
const cleanup = () => fs.rm(dir, { recursive: true, force: true });
|
|
235
|
+
try {
|
|
236
|
+
const file = path.join(dir, 'subconscious.cordis.yml');
|
|
237
|
+
await fs.writeFile(file, text, { mode: 0o600, flag: 'wx' });
|
|
238
|
+
const argvWithFile = plan.argv.map((word, index) =>
|
|
239
|
+
index < plan.templateWords ? word.replaceAll('{tempFile}', file) : word,
|
|
240
|
+
);
|
|
241
|
+
return {
|
|
242
|
+
command: argvWithFile[0],
|
|
243
|
+
args: argvWithFile.slice(1),
|
|
244
|
+
env: plan.env,
|
|
245
|
+
...(plan.input === undefined ? {} : { input: plan.input }),
|
|
246
|
+
cleanup,
|
|
247
|
+
};
|
|
248
|
+
} catch (error) {
|
|
249
|
+
await cleanup();
|
|
250
|
+
throw error;
|
|
89
251
|
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
export async function windowsLaunch(
|
|
255
|
+
id,
|
|
256
|
+
argv,
|
|
257
|
+
env,
|
|
258
|
+
{ tempRoot = os.tmpdir() } = {},
|
|
259
|
+
) {
|
|
260
|
+
const agent = agentById(id);
|
|
261
|
+
const launchEnv = windowsEnv(id, env);
|
|
262
|
+
if (PLANNED.has(id)) return plannedLaunch(agent, argv, launchEnv);
|
|
263
|
+
if (id === 'codex') return codexLaunch(agent, argv, launchEnv, tempRoot);
|
|
264
|
+
if (id === 'deepseek-harness')
|
|
265
|
+
return dshLaunch(agent, argv, launchEnv, tempRoot);
|
|
90
266
|
throw new Error(`No native Windows launcher is available for ${id}`);
|
|
91
267
|
}
|
|
92
268
|
|
|
93
269
|
export async function executeWindowsLaunch(spec, run) {
|
|
94
|
-
try {
|
|
95
|
-
|
|
270
|
+
try {
|
|
271
|
+
const options =
|
|
272
|
+
spec.input === undefined
|
|
273
|
+
? { env: spec.env }
|
|
274
|
+
: { env: spec.env, input: spec.input };
|
|
275
|
+
return await run(spec.command, spec.args, options);
|
|
276
|
+
} finally {
|
|
277
|
+
await spec.cleanup?.();
|
|
278
|
+
}
|
|
96
279
|
}
|
|
@@ -1,29 +1,84 @@
|
|
|
1
1
|
// Windows-only Pi extension. Loaded config is relative to this installed file.
|
|
2
|
-
|
|
2
|
+
|
|
3
3
|
import { randomUUID } from 'node:crypto';
|
|
4
|
-
import {
|
|
4
|
+
import { readFileSync } from 'node:fs';
|
|
5
5
|
import { dirname, join } from 'node:path';
|
|
6
|
+
import { fileURLToPath } from 'node:url';
|
|
7
|
+
|
|
8
|
+
type PiCompactEvent = {
|
|
9
|
+
reason?: unknown;
|
|
10
|
+
preparation?: { tokensBefore?: unknown };
|
|
11
|
+
fromExtension?: unknown;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
type PiContext = {
|
|
15
|
+
sessionManager?: { getSessionId?: () => string | undefined };
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
type WindowsPiConfig = {
|
|
19
|
+
gatewayUrl?: string;
|
|
20
|
+
apiKey?: string;
|
|
21
|
+
};
|
|
6
22
|
|
|
7
|
-
export default function (pi: {
|
|
8
|
-
|
|
9
|
-
|
|
23
|
+
export default function (pi: {
|
|
24
|
+
on: (
|
|
25
|
+
event: string,
|
|
26
|
+
handler: (event: PiCompactEvent, ctx: PiContext) => Promise<void>,
|
|
27
|
+
) => void;
|
|
28
|
+
}) {
|
|
29
|
+
for (const [eventName, phase] of [
|
|
30
|
+
['session_before_compact', 'start'],
|
|
31
|
+
['session_compact', 'end'],
|
|
32
|
+
] as const) {
|
|
33
|
+
pi.on(eventName, async (event, ctx) => {
|
|
10
34
|
const session = ctx?.sessionManager?.getSessionId?.();
|
|
11
35
|
if (!session) return;
|
|
12
36
|
const controller = new AbortController();
|
|
13
37
|
const timer = setTimeout(() => controller.abort(), 1500);
|
|
14
38
|
try {
|
|
15
|
-
const config = JSON.parse(
|
|
39
|
+
const config = JSON.parse(
|
|
40
|
+
readFileSync(
|
|
41
|
+
join(
|
|
42
|
+
dirname(fileURLToPath(import.meta.url)),
|
|
43
|
+
'..',
|
|
44
|
+
'subconscious-windows.json',
|
|
45
|
+
),
|
|
46
|
+
'utf8',
|
|
47
|
+
),
|
|
48
|
+
) as WindowsPiConfig;
|
|
16
49
|
const url = process.env.SUBCONSCIOUS_GATEWAY_URL || config.gatewayUrl;
|
|
17
50
|
const key = process.env.SUBCONSCIOUS_API_KEY || config.apiKey;
|
|
18
51
|
if (!url || !key) return;
|
|
19
|
-
const response = await fetch(
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
52
|
+
const response = await fetch(
|
|
53
|
+
`${url.replace(/\/+$/, '')}/v1/agent-hooks`,
|
|
54
|
+
{
|
|
55
|
+
method: 'POST',
|
|
56
|
+
headers: {
|
|
57
|
+
'content-type': 'application/json',
|
|
58
|
+
authorization: `Bearer ${key}`,
|
|
59
|
+
'x-subconscious-client': 'pi',
|
|
60
|
+
},
|
|
61
|
+
body: JSON.stringify({
|
|
62
|
+
event: 'conversation_compaction',
|
|
63
|
+
conversation_id: session,
|
|
64
|
+
phase,
|
|
65
|
+
hook_event_name: eventName,
|
|
66
|
+
dedupe_key: `${session}:${phase}:${randomUUID()}`,
|
|
67
|
+
metadata: {
|
|
68
|
+
reason: event?.reason,
|
|
69
|
+
tokens_before: event?.preparation?.tokensBefore,
|
|
70
|
+
from_extension: event?.fromExtension,
|
|
71
|
+
},
|
|
72
|
+
}),
|
|
73
|
+
signal: controller.signal,
|
|
74
|
+
},
|
|
75
|
+
);
|
|
24
76
|
await response.body?.cancel();
|
|
25
|
-
} catch {
|
|
26
|
-
|
|
77
|
+
} catch {
|
|
78
|
+
/* observational: never cancel or replace compaction */
|
|
79
|
+
} finally {
|
|
80
|
+
clearTimeout(timer);
|
|
81
|
+
}
|
|
27
82
|
});
|
|
28
83
|
}
|
|
29
84
|
}
|