@code-yeongyu/senpi-codemode 2026.10.5 → 2026.10.7
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/CHANGELOG.md +41 -0
- package/README.md +20 -2
- package/package.json +4 -4
- package/src/bridge/kernel-tools-protocol.ts +1 -1
- package/src/bridge/reserved.ts +10 -0
- package/src/bridges/eval-virtual-schemas.ts +79 -0
- package/src/bridges/handle-bridge.ts +72 -0
- package/src/bridges/reserved-dispatch.ts +20 -2
- package/src/bridges/schema-bridge.ts +4 -0
- package/src/completion/handle.ts +69 -0
- package/src/completion/tool-bridge.ts +31 -12
- package/src/config/feature-settings.ts +110 -0
- package/src/config/memory-settings.ts +8 -0
- package/src/config/settings-constants.ts +7 -0
- package/src/config/settings-overrides.ts +72 -0
- package/src/config/settings.ts +35 -78
- package/src/config/unknown-settings.ts +19 -0
- package/src/extension/bridge-dispatch.ts +41 -0
- package/src/extension/bridge-tool-call.ts +41 -0
- package/src/extension/host-facts.ts +18 -0
- package/src/extension/idle-parking-kernel.ts +272 -0
- package/src/extension/kernel-registration.ts +25 -0
- package/src/extension/kernel-tools-probe.ts +24 -0
- package/src/extension/removed-tool-hints.ts +15 -0
- package/src/extension/runtime-factory.ts +3 -0
- package/src/extension/session-manager-contract.ts +3 -0
- package/src/extension/session-manager.ts +58 -58
- package/src/extension/start-recovery.ts +111 -0
- package/src/handles/completion-handles.ts +202 -0
- package/src/handles/handle-args.ts +103 -0
- package/src/handles/handle-registry.ts +164 -0
- package/src/handles/wait.ts +214 -0
- package/src/handles/watch-queue.ts +102 -0
- package/src/index.ts +99 -94
- package/src/kernels/jl/handles.jl +124 -0
- package/src/kernels/jl/kernel.ts +3 -1
- package/src/kernels/jl/prelude.jl +9 -0
- package/src/kernels/jl/runner.jl +160 -1
- package/src/kernels/js/context-manager.ts +1 -1
- package/src/kernels/js/handles.js +93 -0
- package/src/kernels/js/kernel-tools-define.d.ts +6 -0
- package/src/kernels/js/kernel-tools-define.js +3 -1
- package/src/kernels/js/kernel-tools-registry.d.ts +2 -0
- package/src/kernels/js/kernel-tools-registry.js +18 -9
- package/src/kernels/js/kernel-tools-schema.js +6 -2
- package/src/kernels/js/kernel-tools-types.ts +2 -1
- package/src/kernels/js/worker-global-sizes.js +84 -14
- package/src/kernels/js/worker-runtime.js +10 -1
- package/src/kernels/py/host-cell.ts +42 -0
- package/src/kernels/py/kernel-contract.ts +2 -0
- package/src/kernels/py/kernel.ts +48 -4
- package/src/kernels/py/prelude.py +145 -4
- package/src/kernels/rb/handles.rb +97 -0
- package/src/kernels/rb/kernel.ts +3 -1
- package/src/kernels/rb/paths.rb +37 -0
- package/src/kernels/rb/prelude.rb +15 -38
- package/src/kernels/rb/runner.rb +141 -0
- package/src/kernels/shared/kernel-memory-host.ts +14 -6
- package/src/kernels/{js/kernel-tools-host.ts → shared/kernel-tools-pump.ts} +2 -2
- package/src/kernels/shared/process-group-cpu.ts +139 -0
- package/src/kernels/shared/subprocess-contract.ts +3 -0
- package/src/kernels/shared/subprocess-kernel.ts +33 -1
- package/src/kernels/shared/subprocess-startup.ts +90 -0
- package/src/tool/cell-handler.ts +21 -18
- package/src/tool/cell-reserved-dispatch.ts +51 -0
- package/src/tool/eval-tool-options.ts +3 -0
- package/src/tool/run-eval-cell.ts +7 -6
- package/src/tool/types.ts +15 -0
|
@@ -3,6 +3,9 @@ import { type Static, Type } from "typebox";
|
|
|
3
3
|
import type { KernelMemoryThresholds } from "../bridge/memory-protocol.ts";
|
|
4
4
|
import type { CodemodeSettings, Environment } from "./settings.ts";
|
|
5
5
|
|
|
6
|
+
// The longest delay setTimeout honours (2^31 - 1 ms); a longer one fires after 1 ms.
|
|
7
|
+
export const MAX_IDLE_PARK_MINUTES = 35_791;
|
|
8
|
+
|
|
6
9
|
export const memorySettingsSchema = Type.Object(
|
|
7
10
|
{
|
|
8
11
|
gcWatermarkMb: Type.Optional(Type.Number({ minimum: 0 })),
|
|
@@ -10,6 +13,7 @@ export const memorySettingsSchema = Type.Object(
|
|
|
10
13
|
ceilingMb: Type.Optional(Type.Number({ minimum: 0 })),
|
|
11
14
|
retainedResultsMb: Type.Optional(Type.Number({ minimum: 0 })),
|
|
12
15
|
retainedImagesMb: Type.Optional(Type.Number({ minimum: 0 })),
|
|
16
|
+
idleParkMinutes: Type.Optional(Type.Number({ minimum: 0, maximum: MAX_IDLE_PARK_MINUTES })),
|
|
13
17
|
},
|
|
14
18
|
{ additionalProperties: false },
|
|
15
19
|
);
|
|
@@ -27,6 +31,8 @@ export interface CodemodeMemorySettings {
|
|
|
27
31
|
readonly retainedResultsMb: number;
|
|
28
32
|
/** Disk budget (MiB) for settled-cell images spilled under the session artifacts dir; 0 keeps only the count cap. */
|
|
29
33
|
readonly retainedImagesMb: number;
|
|
34
|
+
/** Minutes with no cell running or queued before a kernel is closed and restarted on the next cell; absent or 0 never parks. */
|
|
35
|
+
readonly idleParkMinutes?: number;
|
|
30
36
|
}
|
|
31
37
|
|
|
32
38
|
export const DEFAULT_MEMORY_GC_WATERMARK_MB = 256;
|
|
@@ -67,6 +73,7 @@ export function mergeMemorySettings(input: CodemodeMemorySettingsInput | undefin
|
|
|
67
73
|
ceilingMb: input?.ceilingMb ?? defaults.ceilingMb,
|
|
68
74
|
retainedResultsMb: input?.retainedResultsMb ?? defaults.retainedResultsMb,
|
|
69
75
|
retainedImagesMb: input?.retainedImagesMb ?? defaults.retainedImagesMb,
|
|
76
|
+
...(input?.idleParkMinutes === undefined ? {} : { idleParkMinutes: input.idleParkMinutes }),
|
|
70
77
|
};
|
|
71
78
|
}
|
|
72
79
|
|
|
@@ -91,6 +98,7 @@ export function validatedMemorySettings(input: CodemodeMemorySettingsInput | und
|
|
|
91
98
|
...defaults,
|
|
92
99
|
retainedResultsMb: merged.retainedResultsMb,
|
|
93
100
|
retainedImagesMb: merged.retainedImagesMb,
|
|
101
|
+
...(merged.idleParkMinutes === undefined ? {} : { idleParkMinutes: merged.idleParkMinutes }),
|
|
94
102
|
},
|
|
95
103
|
warnings: [warning],
|
|
96
104
|
};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export const HARD_LIMIT_ENVIRONMENT_FLAG = "SENPI_CODEMODE_HARD_LIMIT_SECONDS";
|
|
2
|
+
export const FOREGROUND_WINDOW_ENVIRONMENT_FLAG = "SENPI_CODEMODE_FOREGROUND_SECONDS";
|
|
3
|
+
export const RUN_BUDGET_ENVIRONMENT_FLAG = "SENPI_CODEMODE_RUN_BUDGET_SECONDS";
|
|
4
|
+
export const MAX_DETACHED_CELLS_ENVIRONMENT_FLAG = "SENPI_CODEMODE_MAX_DETACHED_CELLS";
|
|
5
|
+
export const DEFAULT_MAX_DETACHED_CELLS = 15;
|
|
6
|
+
|
|
7
|
+
export type Environment = Readonly<Record<string, string | undefined>>;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import type { CodemodeSettings } from "./settings.ts";
|
|
2
|
+
import {
|
|
3
|
+
DEFAULT_MAX_DETACHED_CELLS,
|
|
4
|
+
type Environment,
|
|
5
|
+
FOREGROUND_WINDOW_ENVIRONMENT_FLAG,
|
|
6
|
+
HARD_LIMIT_ENVIRONMENT_FLAG,
|
|
7
|
+
MAX_DETACHED_CELLS_ENVIRONMENT_FLAG,
|
|
8
|
+
RUN_BUDGET_ENVIRONMENT_FLAG,
|
|
9
|
+
} from "./settings-constants.ts";
|
|
10
|
+
|
|
11
|
+
const languageEnvironmentFlags = {
|
|
12
|
+
py: "SENPI_CODEMODE_PY",
|
|
13
|
+
js: "SENPI_CODEMODE_JS",
|
|
14
|
+
rb: "SENPI_CODEMODE_RB",
|
|
15
|
+
jl: "SENPI_CODEMODE_JL",
|
|
16
|
+
} as const;
|
|
17
|
+
|
|
18
|
+
export function resolveEnabledLanguages(
|
|
19
|
+
settings: CodemodeSettings,
|
|
20
|
+
env: Environment = process.env,
|
|
21
|
+
): CodemodeSettings["languages"] {
|
|
22
|
+
return {
|
|
23
|
+
py: resolveLanguage(settings.languages.py, env[languageEnvironmentFlags.py]),
|
|
24
|
+
js: resolveLanguage(settings.languages.js, env[languageEnvironmentFlags.js]),
|
|
25
|
+
rb: resolveLanguage(settings.languages.rb, env[languageEnvironmentFlags.rb]),
|
|
26
|
+
jl: resolveLanguage(settings.languages.jl, env[languageEnvironmentFlags.jl]),
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Environment override wins over the settings file; a non-positive or malformed value is ignored. */
|
|
31
|
+
export function resolveHardLimitSeconds(settings: CodemodeSettings, env: Environment = process.env): number {
|
|
32
|
+
return positiveSecondsOverride(env[HARD_LIMIT_ENVIRONMENT_FLAG]) ?? settings.hardLimitSeconds;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Environment override wins over the settings file; a non-positive or malformed value is ignored. */
|
|
36
|
+
export function resolveForegroundWindowSeconds(settings: CodemodeSettings, env: Environment = process.env): number {
|
|
37
|
+
return positiveSecondsOverride(env[FOREGROUND_WINDOW_ENVIRONMENT_FLAG]) ?? settings.foregroundWindowSeconds;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Environment override wins over the settings file; a non-positive or malformed value is ignored. */
|
|
41
|
+
export function resolveRunBudgetSeconds(settings: CodemodeSettings, env: Environment = process.env): number {
|
|
42
|
+
return positiveSecondsOverride(env[RUN_BUDGET_ENVIRONMENT_FLAG]) ?? settings.runBudgetSeconds;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Uses the same positive-integer environment parsing as the run budget. */
|
|
46
|
+
export function resolveMaxDetachedCells(settings: CodemodeSettings, env: Environment = process.env): number {
|
|
47
|
+
return (
|
|
48
|
+
positiveSecondsOverride(env[MAX_DETACHED_CELLS_ENVIRONMENT_FLAG]) ??
|
|
49
|
+
settings.maxDetachedCells ??
|
|
50
|
+
DEFAULT_MAX_DETACHED_CELLS
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function positiveSecondsOverride(value: string | undefined): number | undefined {
|
|
55
|
+
if (value === undefined) return undefined;
|
|
56
|
+
const parsed = Number.parseInt(value, 10);
|
|
57
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : undefined;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function resolveLanguage(fileSetting: boolean, environmentValue: string | undefined): boolean {
|
|
61
|
+
if (environmentValue === undefined) return fileSetting;
|
|
62
|
+
switch (environmentValue.trim().toLowerCase()) {
|
|
63
|
+
case "0":
|
|
64
|
+
case "false":
|
|
65
|
+
return false;
|
|
66
|
+
case "1":
|
|
67
|
+
case "true":
|
|
68
|
+
return true;
|
|
69
|
+
default:
|
|
70
|
+
return fileSetting;
|
|
71
|
+
}
|
|
72
|
+
}
|
package/src/config/settings.ts
CHANGED
|
@@ -4,7 +4,10 @@ import { join } from "node:path";
|
|
|
4
4
|
import type { Static } from "typebox";
|
|
5
5
|
import { Type } from "typebox";
|
|
6
6
|
import { Check } from "typebox/value";
|
|
7
|
+
import { type CodemodeFeatureSettings, featureSettingsProperties, pickFeatureSettings } from "./feature-settings.ts";
|
|
7
8
|
import { type CodemodeMemorySettings, memorySettingsSchema, validatedMemorySettings } from "./memory-settings.ts";
|
|
9
|
+
import { DEFAULT_MAX_DETACHED_CELLS } from "./settings-constants.ts";
|
|
10
|
+
import { withoutUnknownTopLevelKeys } from "./unknown-settings.ts";
|
|
8
11
|
|
|
9
12
|
export const codemodeSettingsSchema = Type.Object(
|
|
10
13
|
{
|
|
@@ -15,6 +18,7 @@ export const codemodeSettingsSchema = Type.Object(
|
|
|
15
18
|
js: Type.Optional(Type.Boolean()),
|
|
16
19
|
rb: Type.Optional(Type.Boolean()),
|
|
17
20
|
jl: Type.Optional(Type.Boolean()),
|
|
21
|
+
pyInterpreter: Type.Optional(Type.String({ minLength: 1 })),
|
|
18
22
|
},
|
|
19
23
|
{ additionalProperties: false },
|
|
20
24
|
),
|
|
@@ -45,6 +49,7 @@ export const codemodeSettingsSchema = Type.Object(
|
|
|
45
49
|
),
|
|
46
50
|
statusEvents: Type.Optional(Type.Boolean()),
|
|
47
51
|
memory: Type.Optional(memorySettingsSchema),
|
|
52
|
+
...featureSettingsProperties,
|
|
48
53
|
},
|
|
49
54
|
{ additionalProperties: false },
|
|
50
55
|
);
|
|
@@ -61,12 +66,14 @@ export interface CodemodeOutputSink {
|
|
|
61
66
|
readonly maxColumns: number;
|
|
62
67
|
}
|
|
63
68
|
|
|
64
|
-
export interface CodemodeSettings {
|
|
69
|
+
export interface CodemodeSettings extends CodemodeFeatureSettings {
|
|
65
70
|
readonly languages: {
|
|
66
71
|
readonly py: boolean;
|
|
67
72
|
readonly js: boolean;
|
|
68
73
|
readonly rb: boolean;
|
|
69
74
|
readonly jl: boolean;
|
|
75
|
+
/** Explicit Python interpreter; unset keeps today's PATH detection. */
|
|
76
|
+
readonly pyInterpreter?: string;
|
|
70
77
|
};
|
|
71
78
|
/** Idle time an interactive call blocks the turn before the cell detaches; capped by the foreground window. */
|
|
72
79
|
readonly cellTimeoutSeconds: number;
|
|
@@ -118,8 +125,6 @@ export interface LoadedCodemodeSettings {
|
|
|
118
125
|
*/
|
|
119
126
|
export const DEFAULT_HARD_LIMIT_SECONDS = 1800;
|
|
120
127
|
|
|
121
|
-
export const HARD_LIMIT_ENVIRONMENT_FLAG = "SENPI_CODEMODE_HARD_LIMIT_SECONDS";
|
|
122
|
-
|
|
123
128
|
/**
|
|
124
129
|
* Bash parity: `terminal/tools/foreground-window.ts` auto-detaches a still-running bash command to a
|
|
125
130
|
* background session at 60s regardless of its `timeout` kill deadline. An eval cell gets the same
|
|
@@ -128,8 +133,6 @@ export const HARD_LIMIT_ENVIRONMENT_FLAG = "SENPI_CODEMODE_HARD_LIMIT_SECONDS";
|
|
|
128
133
|
*/
|
|
129
134
|
export const DEFAULT_FOREGROUND_WINDOW_SECONDS = 60;
|
|
130
135
|
|
|
131
|
-
export const FOREGROUND_WINDOW_ENVIRONMENT_FLAG = "SENPI_CODEMODE_FOREGROUND_SECONDS";
|
|
132
|
-
|
|
133
136
|
/**
|
|
134
137
|
* One language kernel runs one cell at a time and a killed JavaScript cell that cannot settle
|
|
135
138
|
* cooperatively restarts its worker, so a runaway cell costs far more than a runaway bash command:
|
|
@@ -137,11 +140,6 @@ export const FOREGROUND_WINDOW_ENVIRONMENT_FLAG = "SENPI_CODEMODE_FOREGROUND_SEC
|
|
|
137
140
|
*/
|
|
138
141
|
export const DEFAULT_RUN_BUDGET_SECONDS = 300;
|
|
139
142
|
|
|
140
|
-
export const DEFAULT_MAX_DETACHED_CELLS = 15;
|
|
141
|
-
|
|
142
|
-
export const RUN_BUDGET_ENVIRONMENT_FLAG = "SENPI_CODEMODE_RUN_BUDGET_SECONDS";
|
|
143
|
-
export const MAX_DETACHED_CELLS_ENVIRONMENT_FLAG = "SENPI_CODEMODE_MAX_DETACHED_CELLS";
|
|
144
|
-
|
|
145
143
|
// OMP settings-schema.ts:3211-3299 has language/path settings only; eval.ts:427
|
|
146
144
|
// defaults timeout to 30s, and codemode pins concurrency-bridge.ts:30 width to 4.
|
|
147
145
|
export const defaultCodemodeSettings: ResolvedCodemodeSettings = {
|
|
@@ -169,15 +167,6 @@ export const defaultCodemodeSettings: ResolvedCodemodeSettings = {
|
|
|
169
167
|
memory: validatedMemorySettings(undefined).settings,
|
|
170
168
|
};
|
|
171
169
|
|
|
172
|
-
const languageEnvironmentFlags = {
|
|
173
|
-
py: "SENPI_CODEMODE_PY",
|
|
174
|
-
js: "SENPI_CODEMODE_JS",
|
|
175
|
-
rb: "SENPI_CODEMODE_RB",
|
|
176
|
-
jl: "SENPI_CODEMODE_JL",
|
|
177
|
-
} as const;
|
|
178
|
-
|
|
179
|
-
export type Environment = Readonly<Record<string, string | undefined>>;
|
|
180
|
-
|
|
181
170
|
export async function loadCodemodeSettings(options: LoadCodemodeSettingsOptions = {}): Promise<LoadedCodemodeSettings> {
|
|
182
171
|
const cwd = options.cwd ?? process.cwd();
|
|
183
172
|
const homeDir = options.homeDir ?? homedir();
|
|
@@ -193,48 +182,6 @@ export async function loadCodemodeSettings(options: LoadCodemodeSettingsOptions
|
|
|
193
182
|
return { settings: defaultCodemodeSettings, source: null, warnings: [] };
|
|
194
183
|
}
|
|
195
184
|
|
|
196
|
-
export function resolveEnabledLanguages(
|
|
197
|
-
settings: CodemodeSettings,
|
|
198
|
-
env: Environment = process.env,
|
|
199
|
-
): CodemodeSettings["languages"] {
|
|
200
|
-
return {
|
|
201
|
-
py: resolveLanguage(settings.languages.py, env[languageEnvironmentFlags.py]),
|
|
202
|
-
js: resolveLanguage(settings.languages.js, env[languageEnvironmentFlags.js]),
|
|
203
|
-
rb: resolveLanguage(settings.languages.rb, env[languageEnvironmentFlags.rb]),
|
|
204
|
-
jl: resolveLanguage(settings.languages.jl, env[languageEnvironmentFlags.jl]),
|
|
205
|
-
};
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
/** Environment override wins over the settings file; a non-positive or malformed value is ignored. */
|
|
209
|
-
export function resolveHardLimitSeconds(settings: CodemodeSettings, env: Environment = process.env): number {
|
|
210
|
-
return positiveSecondsOverride(env[HARD_LIMIT_ENVIRONMENT_FLAG]) ?? settings.hardLimitSeconds;
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
/** Environment override wins over the settings file; a non-positive or malformed value is ignored. */
|
|
214
|
-
export function resolveForegroundWindowSeconds(settings: CodemodeSettings, env: Environment = process.env): number {
|
|
215
|
-
return positiveSecondsOverride(env[FOREGROUND_WINDOW_ENVIRONMENT_FLAG]) ?? settings.foregroundWindowSeconds;
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
/** Environment override wins over the settings file; a non-positive or malformed value is ignored. */
|
|
219
|
-
export function resolveRunBudgetSeconds(settings: CodemodeSettings, env: Environment = process.env): number {
|
|
220
|
-
return positiveSecondsOverride(env[RUN_BUDGET_ENVIRONMENT_FLAG]) ?? settings.runBudgetSeconds;
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
/** Uses the same positive-integer environment parsing as the run budget. */
|
|
224
|
-
export function resolveMaxDetachedCells(settings: CodemodeSettings, env: Environment = process.env): number {
|
|
225
|
-
return (
|
|
226
|
-
positiveSecondsOverride(env[MAX_DETACHED_CELLS_ENVIRONMENT_FLAG]) ??
|
|
227
|
-
settings.maxDetachedCells ??
|
|
228
|
-
DEFAULT_MAX_DETACHED_CELLS
|
|
229
|
-
);
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
function positiveSecondsOverride(value: string | undefined): number | undefined {
|
|
233
|
-
if (value === undefined) return undefined;
|
|
234
|
-
const parsed = Number.parseInt(value, 10);
|
|
235
|
-
return Number.isFinite(parsed) && parsed > 0 ? parsed : undefined;
|
|
236
|
-
}
|
|
237
|
-
|
|
238
185
|
async function loadSettingsFile(path: string): Promise<LoadedCodemodeSettings> {
|
|
239
186
|
const raw = await readFile(path, "utf8");
|
|
240
187
|
let parsed: unknown;
|
|
@@ -249,15 +196,21 @@ async function loadSettingsFile(path: string): Promise<LoadedCodemodeSettings> {
|
|
|
249
196
|
};
|
|
250
197
|
}
|
|
251
198
|
|
|
252
|
-
|
|
199
|
+
const known = withoutUnknownTopLevelKeys(parsed, new Set(Object.keys(codemodeSettingsSchema.properties)), path);
|
|
200
|
+
const candidate = known.value;
|
|
201
|
+
if (!Check(codemodeSettingsSchema, candidate)) {
|
|
253
202
|
return {
|
|
254
203
|
settings: defaultCodemodeSettings,
|
|
255
204
|
source: path,
|
|
256
|
-
warnings: [`Invalid codemode settings in ${path}. Falling back to codemode defaults.`],
|
|
205
|
+
warnings: [...known.warnings, `Invalid codemode settings in ${path}. Falling back to codemode defaults.`],
|
|
257
206
|
};
|
|
258
207
|
}
|
|
259
208
|
|
|
260
|
-
return {
|
|
209
|
+
return {
|
|
210
|
+
settings: mergeSettings(candidate),
|
|
211
|
+
source: path,
|
|
212
|
+
warnings: [...known.warnings, ...validatedMemorySettings(candidate.memory).warnings],
|
|
213
|
+
};
|
|
261
214
|
}
|
|
262
215
|
|
|
263
216
|
function mergeSettings(input: CodemodeSettingsInput): ResolvedCodemodeSettings {
|
|
@@ -267,6 +220,7 @@ function mergeSettings(input: CodemodeSettingsInput): ResolvedCodemodeSettings {
|
|
|
267
220
|
js: input.languages?.js ?? defaultCodemodeSettings.languages.js,
|
|
268
221
|
rb: input.languages?.rb ?? defaultCodemodeSettings.languages.rb,
|
|
269
222
|
jl: input.languages?.jl ?? defaultCodemodeSettings.languages.jl,
|
|
223
|
+
...(input.languages?.pyInterpreter === undefined ? {} : { pyInterpreter: input.languages.pyInterpreter }),
|
|
270
224
|
},
|
|
271
225
|
cellTimeoutSeconds: input.cellTimeoutSeconds ?? defaultCodemodeSettings.cellTimeoutSeconds,
|
|
272
226
|
foregroundWindowSeconds: input.foregroundWindowSeconds ?? defaultCodemodeSettings.foregroundWindowSeconds,
|
|
@@ -284,23 +238,10 @@ function mergeSettings(input: CodemodeSettingsInput): ResolvedCodemodeSettings {
|
|
|
284
238
|
},
|
|
285
239
|
statusEvents: input.statusEvents ?? defaultCodemodeSettings.statusEvents,
|
|
286
240
|
memory: validatedMemorySettings(input.memory).settings,
|
|
241
|
+
...pickFeatureSettings(input),
|
|
287
242
|
};
|
|
288
243
|
}
|
|
289
244
|
|
|
290
|
-
function resolveLanguage(fileSetting: boolean, environmentValue: string | undefined): boolean {
|
|
291
|
-
if (environmentValue === undefined) return fileSetting;
|
|
292
|
-
switch (environmentValue.trim().toLowerCase()) {
|
|
293
|
-
case "0":
|
|
294
|
-
case "false":
|
|
295
|
-
return false;
|
|
296
|
-
case "1":
|
|
297
|
-
case "true":
|
|
298
|
-
return true;
|
|
299
|
-
default:
|
|
300
|
-
return fileSetting;
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
|
|
304
245
|
async function fileExists(path: string): Promise<boolean> {
|
|
305
246
|
try {
|
|
306
247
|
await access(path);
|
|
@@ -309,3 +250,19 @@ async function fileExists(path: string): Promise<boolean> {
|
|
|
309
250
|
return false;
|
|
310
251
|
}
|
|
311
252
|
}
|
|
253
|
+
|
|
254
|
+
export {
|
|
255
|
+
DEFAULT_MAX_DETACHED_CELLS,
|
|
256
|
+
type Environment,
|
|
257
|
+
FOREGROUND_WINDOW_ENVIRONMENT_FLAG,
|
|
258
|
+
HARD_LIMIT_ENVIRONMENT_FLAG,
|
|
259
|
+
MAX_DETACHED_CELLS_ENVIRONMENT_FLAG,
|
|
260
|
+
RUN_BUDGET_ENVIRONMENT_FLAG,
|
|
261
|
+
} from "./settings-constants.ts";
|
|
262
|
+
export {
|
|
263
|
+
resolveEnabledLanguages,
|
|
264
|
+
resolveForegroundWindowSeconds,
|
|
265
|
+
resolveHardLimitSeconds,
|
|
266
|
+
resolveMaxDetachedCells,
|
|
267
|
+
resolveRunBudgetSeconds,
|
|
268
|
+
} from "./settings-overrides.ts";
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A settings file written for a newer senpi may name keys this version does not know: they are dropped with
|
|
3
|
+
* one warning each instead of failing the whole file, so the user's other settings still apply. Only the
|
|
4
|
+
* top level is lenient; the known nested objects keep their strict schemas.
|
|
5
|
+
*/
|
|
6
|
+
export function withoutUnknownTopLevelKeys(
|
|
7
|
+
parsed: unknown,
|
|
8
|
+
known: ReadonlySet<string>,
|
|
9
|
+
path: string,
|
|
10
|
+
): { readonly value: unknown; readonly warnings: string[] } {
|
|
11
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) return { value: parsed, warnings: [] };
|
|
12
|
+
const kept: Record<string, unknown> = {};
|
|
13
|
+
const warnings: string[] = [];
|
|
14
|
+
for (const [key, value] of Object.entries(parsed)) {
|
|
15
|
+
if (known.has(key)) kept[key] = value;
|
|
16
|
+
else warnings.push(`Unknown codemode setting "${key}" in ${path} is ignored; the other settings still apply.`);
|
|
17
|
+
}
|
|
18
|
+
return { value: kept, warnings };
|
|
19
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { ExtensionContext } from "@code-yeongyu/senpi";
|
|
2
|
+
import type { BridgeHttpCompletionRequest } from "../bridge/http-server.ts";
|
|
3
|
+
import { startCompletionHandle, wantsCompletionHandle } from "../completion/handle.ts";
|
|
4
|
+
import type { CompletionRequest } from "../completion/handler.ts";
|
|
5
|
+
import { resolveHardLimitSeconds } from "../config/settings.ts";
|
|
6
|
+
import type { CreateCodemodeSessionManagerOptions } from "./session-manager-contract.ts";
|
|
7
|
+
|
|
8
|
+
export interface BridgeDispatchContext {
|
|
9
|
+
/** The context of the cell that last reached the manager; the handle host and completion owner are read from it. */
|
|
10
|
+
readonly context: ExtensionContext | undefined;
|
|
11
|
+
readonly contextFor: (signal: AbortSignal) => ExtensionContext;
|
|
12
|
+
readonly requireContext: () => ExtensionContext;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* `completion(prompt, handle=True)` from a subprocess kernel: the handle lives in the session registry and is
|
|
17
|
+
* bounded by the hard limit measured from this request, the tightest bound the bridge knows for its cell.
|
|
18
|
+
*/
|
|
19
|
+
export async function dispatchBridgeCompletion(
|
|
20
|
+
options: CreateCodemodeSessionManagerOptions,
|
|
21
|
+
request: BridgeHttpCompletionRequest,
|
|
22
|
+
dispatch: BridgeDispatchContext,
|
|
23
|
+
): Promise<unknown> {
|
|
24
|
+
const completion: CompletionRequest = { prompt: request.prompt, opts: request.opts };
|
|
25
|
+
if (!wantsCompletionHandle(request.opts)) {
|
|
26
|
+
return await options.complete(completion, dispatch.contextFor(request.signal));
|
|
27
|
+
}
|
|
28
|
+
const registry = options.handles;
|
|
29
|
+
if (registry === undefined) {
|
|
30
|
+
throw Object.assign(new Error("eval_wait_unavailable: completion handles are not available in this session"), {
|
|
31
|
+
code: "eval_wait_unavailable",
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
return startCompletionHandle({
|
|
35
|
+
registry,
|
|
36
|
+
request: completion,
|
|
37
|
+
complete: options.complete,
|
|
38
|
+
ctx: dispatch.requireContext(),
|
|
39
|
+
deadlineMs: Date.now() + resolveHardLimitSeconds(options.settings) * 1_000,
|
|
40
|
+
});
|
|
41
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { EvalHandleHost } from "@code-yeongyu/senpi";
|
|
2
|
+
import { isReservedToolName, runReservedTool } from "../bridges/reserved-dispatch.ts";
|
|
3
|
+
import { defaultCodemodeSettings } from "../config/settings.ts";
|
|
4
|
+
import { marshalToolResult } from "../tool/image.ts";
|
|
5
|
+
import type { CreateCodemodeSessionManagerOptions } from "./session-manager-contract.ts";
|
|
6
|
+
|
|
7
|
+
export interface BridgeToolCallRequest {
|
|
8
|
+
readonly toolName: string;
|
|
9
|
+
readonly args: unknown;
|
|
10
|
+
readonly callId: string;
|
|
11
|
+
readonly signal: AbortSignal;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// Subprocess kernels (py/rb/jl) reach the host only through this route, so every reply
|
|
15
|
+
// must match the in-process JS path in tool/cell-handler.ts: reserved helper names dispatch
|
|
16
|
+
// through runReservedTool (forwarding them made agent() fail with "Unknown tool __agent__"),
|
|
17
|
+
// and ordinary tool results are marshalled to { text, images, details, hasError } — the raw
|
|
18
|
+
// { content } shape left python cells unable to reach tool.read image blocks.
|
|
19
|
+
export async function routeBridgeToolCall(
|
|
20
|
+
options: Pick<CreateCodemodeSessionManagerOptions, "executeTool" | "listTools" | "settings" | "handles">,
|
|
21
|
+
request: BridgeToolCallRequest,
|
|
22
|
+
evalHandleHost?: EvalHandleHost,
|
|
23
|
+
): Promise<unknown> {
|
|
24
|
+
if (!isReservedToolName(request.toolName)) {
|
|
25
|
+
return marshalToolResult(await options.executeTool(request.toolName, request.args, { signal: request.signal }));
|
|
26
|
+
}
|
|
27
|
+
const taskTools = options.settings.taskTools ?? defaultCodemodeSettings.taskTools;
|
|
28
|
+
return await runReservedTool(request.toolName, {
|
|
29
|
+
callId: request.callId,
|
|
30
|
+
args: request.args,
|
|
31
|
+
executeTool: options.executeTool,
|
|
32
|
+
taskToolName: taskTools.task,
|
|
33
|
+
taskOutputToolName: taskTools.output,
|
|
34
|
+
listTools: options.listTools,
|
|
35
|
+
signal: request.signal,
|
|
36
|
+
emitStatus: () => {},
|
|
37
|
+
marshalToolResult,
|
|
38
|
+
...(options.handles === undefined ? {} : { handles: options.handles }),
|
|
39
|
+
...(evalHandleHost === undefined ? {} : { evalHandleHost }),
|
|
40
|
+
});
|
|
41
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import * as os from "node:os";
|
|
2
|
+
import { jsRuntimeLabel } from "./runtime-info.ts";
|
|
3
|
+
|
|
4
|
+
/** The one-line host description the eval prompt shows (platform, CPU, cores, JS runtime). */
|
|
5
|
+
export function hostLine(): string {
|
|
6
|
+
const cpu = os.cpus()[0]?.model?.trim();
|
|
7
|
+
return [`${os.platform()} ${os.arch()}`, cpu, `${os.availableParallelism()} cores`, jsRuntimeLabel()]
|
|
8
|
+
.filter((part): part is string => !!part)
|
|
9
|
+
.join(" \u00b7 ");
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/** The model id carried by a `model_select` event, when it has one. */
|
|
13
|
+
export function modelIdFrom(event: unknown): string | undefined {
|
|
14
|
+
if (typeof event !== "object" || event === null || !("model" in event)) return undefined;
|
|
15
|
+
const model = event.model;
|
|
16
|
+
if (typeof model !== "object" || model === null || !("id" in model)) return undefined;
|
|
17
|
+
return typeof model.id === "string" ? model.id : undefined;
|
|
18
|
+
}
|