@aerovato/operator-helper 1.6.1 → 1.7.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 +0 -4
- package/dist/index.js +246 -269
- package/package.json +1 -1
- package/dist/code-puppy/register_callbacks.py +0 -407
package/README.md
CHANGED
|
@@ -32,9 +32,6 @@ operator-helper install pi
|
|
|
32
32
|
|
|
33
33
|
# DeepSeek Harness
|
|
34
34
|
operator-helper install deepseek
|
|
35
|
-
|
|
36
|
-
# Code Puppy
|
|
37
|
-
operator-helper install code-puppy
|
|
38
35
|
```
|
|
39
36
|
|
|
40
37
|
Restart the harness, then run `/operator:user-init` in a new conversation to begin setup.
|
|
@@ -52,7 +49,6 @@ operator-helper install opencode
|
|
|
52
49
|
operator-helper install opencode-v2
|
|
53
50
|
operator-helper install pi
|
|
54
51
|
operator-helper install deepseek
|
|
55
|
-
operator-helper install code-puppy
|
|
56
52
|
```
|
|
57
53
|
|
|
58
54
|
Operator Helper automatically checks for and installs updates when commands run. If it cannot determine whether Bun or npm owns the global installation, it asks for a manual update using the original installation method.
|
package/dist/index.js
CHANGED
|
@@ -15319,7 +15319,7 @@ var log = (...args) => consoleWith((console2) => sync(() => {
|
|
|
15319
15319
|
// package.json
|
|
15320
15320
|
var package_default = {
|
|
15321
15321
|
name: "@aerovato/operator-helper",
|
|
15322
|
-
version: "1.
|
|
15322
|
+
version: "1.7.0",
|
|
15323
15323
|
description: "Installation and setup CLI for Operator Memory",
|
|
15324
15324
|
license: "BSD-3-Clause",
|
|
15325
15325
|
author: "Aerovato Research",
|
|
@@ -15368,6 +15368,8 @@ var package_default = {
|
|
|
15368
15368
|
type: "module"
|
|
15369
15369
|
};
|
|
15370
15370
|
|
|
15371
|
+
// src/commands/common.ts
|
|
15372
|
+
import { lstat, readFile as readFile3 } from "node:fs/promises";
|
|
15371
15373
|
// src/utils.ts
|
|
15372
15374
|
function getErrorMessage(error) {
|
|
15373
15375
|
return error instanceof Error ? error.message : String(error);
|
|
@@ -15467,6 +15469,69 @@ function walkFiles(root, directory) {
|
|
|
15467
15469
|
});
|
|
15468
15470
|
}
|
|
15469
15471
|
|
|
15472
|
+
// src/commands/common.ts
|
|
15473
|
+
function fileFailure(error) {
|
|
15474
|
+
return { exitCode: 1, output: fileErrorMessage(error) };
|
|
15475
|
+
}
|
|
15476
|
+
function fileErrorMessage(error) {
|
|
15477
|
+
if (error instanceof PathKindError) {
|
|
15478
|
+
return `✗ ${error.path}: Expected a ${error.expected}, found ${error.actual}`;
|
|
15479
|
+
}
|
|
15480
|
+
const path = error.reason instanceof SystemError ? `${error.reason.pathOrDescriptor}: ` : "";
|
|
15481
|
+
return `✗ ${path}${error.reason.description ?? error.message}`;
|
|
15482
|
+
}
|
|
15483
|
+
function runProcess(executable, arguments_, cwd, environment) {
|
|
15484
|
+
return gen2(function* () {
|
|
15485
|
+
const spawner = yield* ChildProcessSpawner;
|
|
15486
|
+
const command = make10(executable, [...arguments_], {
|
|
15487
|
+
cwd,
|
|
15488
|
+
env: environment ?? undefined,
|
|
15489
|
+
extendEnv: true
|
|
15490
|
+
});
|
|
15491
|
+
const execution = yield* scoped2(gen2(function* () {
|
|
15492
|
+
const handle = yield* spawner.spawn(command);
|
|
15493
|
+
const [stdout, stderr, exitCode] = yield* all2([
|
|
15494
|
+
handle.stdout.pipe(decodeText(), mkString),
|
|
15495
|
+
handle.stderr.pipe(decodeText(), mkString),
|
|
15496
|
+
handle.exitCode
|
|
15497
|
+
], { concurrency: "unbounded" });
|
|
15498
|
+
return { stdout: stdout.trim(), stderr: stderr.trim(), exitCode: Number(exitCode) };
|
|
15499
|
+
})).pipe(result2);
|
|
15500
|
+
if (isFailure2(execution)) {
|
|
15501
|
+
const error = execution.failure instanceof PlatformError ? execution.failure.reason.description ?? execution.failure.message : getErrorMessage(execution.failure);
|
|
15502
|
+
return { ok: false, error };
|
|
15503
|
+
}
|
|
15504
|
+
return {
|
|
15505
|
+
ok: true,
|
|
15506
|
+
...execution.success,
|
|
15507
|
+
output: [execution.success.stdout, execution.success.stderr].filter(Boolean).join(`
|
|
15508
|
+
`)
|
|
15509
|
+
};
|
|
15510
|
+
});
|
|
15511
|
+
}
|
|
15512
|
+
async function pathKind(path) {
|
|
15513
|
+
try {
|
|
15514
|
+
const info = await lstat(path);
|
|
15515
|
+
return info.isFile() ? "file" : info.isDirectory() ? "directory" : "other";
|
|
15516
|
+
} catch (error) {
|
|
15517
|
+
if (isNodeError(error) && error.code === "ENOENT")
|
|
15518
|
+
return "missing";
|
|
15519
|
+
throw error;
|
|
15520
|
+
}
|
|
15521
|
+
}
|
|
15522
|
+
async function readOptionalFile(path) {
|
|
15523
|
+
try {
|
|
15524
|
+
return await readFile3(path, "utf8");
|
|
15525
|
+
} catch (error) {
|
|
15526
|
+
if (isNodeError(error) && error.code === "ENOENT")
|
|
15527
|
+
return null;
|
|
15528
|
+
throw error;
|
|
15529
|
+
}
|
|
15530
|
+
}
|
|
15531
|
+
function isNodeError(error) {
|
|
15532
|
+
return error instanceof Error && "code" in error;
|
|
15533
|
+
}
|
|
15534
|
+
|
|
15470
15535
|
// src/output.ts
|
|
15471
15536
|
var import_cli_table3 = __toESM(require_table(), 1);
|
|
15472
15537
|
var BORDERLESS = {
|
|
@@ -15825,9 +15890,6 @@ operator-helper install pi
|
|
|
15825
15890
|
|
|
15826
15891
|
# Install DeepSeek Harness plugin
|
|
15827
15892
|
operator-helper install deepseek
|
|
15828
|
-
|
|
15829
|
-
# Install Code Puppy plugin
|
|
15830
|
-
operator-helper install code-puppy
|
|
15831
15893
|
\`\`\`
|
|
15832
15894
|
`;
|
|
15833
15895
|
|
|
@@ -16153,70 +16215,6 @@ function readTemplate(path) {
|
|
|
16153
16215
|
return TEMPLATES[path];
|
|
16154
16216
|
}
|
|
16155
16217
|
|
|
16156
|
-
// src/commands/common.ts
|
|
16157
|
-
import { lstat, readFile as readFile3 } from "node:fs/promises";
|
|
16158
|
-
function fileFailure(error) {
|
|
16159
|
-
return { exitCode: 1, output: fileErrorMessage(error) };
|
|
16160
|
-
}
|
|
16161
|
-
function fileErrorMessage(error) {
|
|
16162
|
-
if (error instanceof PathKindError) {
|
|
16163
|
-
return `✗ ${error.path}: Expected a ${error.expected}, found ${error.actual}`;
|
|
16164
|
-
}
|
|
16165
|
-
const path = error.reason instanceof SystemError ? `${error.reason.pathOrDescriptor}: ` : "";
|
|
16166
|
-
return `✗ ${path}${error.reason.description ?? error.message}`;
|
|
16167
|
-
}
|
|
16168
|
-
function runProcess(executable, arguments_, cwd, environment) {
|
|
16169
|
-
return gen2(function* () {
|
|
16170
|
-
const spawner = yield* ChildProcessSpawner;
|
|
16171
|
-
const command = make10(executable, [...arguments_], {
|
|
16172
|
-
cwd,
|
|
16173
|
-
env: environment ?? undefined,
|
|
16174
|
-
extendEnv: true
|
|
16175
|
-
});
|
|
16176
|
-
const execution = yield* scoped2(gen2(function* () {
|
|
16177
|
-
const handle = yield* spawner.spawn(command);
|
|
16178
|
-
const [stdout, stderr, exitCode] = yield* all2([
|
|
16179
|
-
handle.stdout.pipe(decodeText(), mkString),
|
|
16180
|
-
handle.stderr.pipe(decodeText(), mkString),
|
|
16181
|
-
handle.exitCode
|
|
16182
|
-
], { concurrency: "unbounded" });
|
|
16183
|
-
return { stdout: stdout.trim(), stderr: stderr.trim(), exitCode: Number(exitCode) };
|
|
16184
|
-
})).pipe(result2);
|
|
16185
|
-
if (isFailure2(execution)) {
|
|
16186
|
-
const error = execution.failure instanceof PlatformError ? execution.failure.reason.description ?? execution.failure.message : getErrorMessage(execution.failure);
|
|
16187
|
-
return { ok: false, error };
|
|
16188
|
-
}
|
|
16189
|
-
return {
|
|
16190
|
-
ok: true,
|
|
16191
|
-
...execution.success,
|
|
16192
|
-
output: [execution.success.stdout, execution.success.stderr].filter(Boolean).join(`
|
|
16193
|
-
`)
|
|
16194
|
-
};
|
|
16195
|
-
});
|
|
16196
|
-
}
|
|
16197
|
-
async function pathKind(path) {
|
|
16198
|
-
try {
|
|
16199
|
-
const info = await lstat(path);
|
|
16200
|
-
return info.isFile() ? "file" : info.isDirectory() ? "directory" : "other";
|
|
16201
|
-
} catch (error) {
|
|
16202
|
-
if (isNodeError(error) && error.code === "ENOENT")
|
|
16203
|
-
return "missing";
|
|
16204
|
-
throw error;
|
|
16205
|
-
}
|
|
16206
|
-
}
|
|
16207
|
-
async function readOptionalFile(path) {
|
|
16208
|
-
try {
|
|
16209
|
-
return await readFile3(path, "utf8");
|
|
16210
|
-
} catch (error) {
|
|
16211
|
-
if (isNodeError(error) && error.code === "ENOENT")
|
|
16212
|
-
return null;
|
|
16213
|
-
throw error;
|
|
16214
|
-
}
|
|
16215
|
-
}
|
|
16216
|
-
function isNodeError(error) {
|
|
16217
|
-
return error instanceof Error && "code" in error;
|
|
16218
|
-
}
|
|
16219
|
-
|
|
16220
16218
|
// src/commands/index/init.ts
|
|
16221
16219
|
var PARTITION_ROOTS = [".operator-shared", ".operator"];
|
|
16222
16220
|
var indexInit = fn2("indexInit")((context) => gen2(function* () {
|
|
@@ -16424,83 +16422,62 @@ var indexLint = fn2("indexLint")(function* (context) {
|
|
|
16424
16422
|
};
|
|
16425
16423
|
});
|
|
16426
16424
|
|
|
16427
|
-
// src/commands/install/code
|
|
16428
|
-
|
|
16429
|
-
|
|
16430
|
-
|
|
16431
|
-
|
|
16432
|
-
|
|
16433
|
-
|
|
16434
|
-
|
|
16435
|
-
|
|
16436
|
-
|
|
16437
|
-
|
|
16438
|
-
const source = await readFile4(await codePuppyAdapterPath(), "utf8");
|
|
16439
|
-
const plugins = join2(context.home, ".code_puppy", "plugins");
|
|
16440
|
-
const target = join2(plugins, "operator");
|
|
16441
|
-
const marker = join2(target, CODE_PUPPY_MARKER_FILE);
|
|
16442
|
-
const callbacks = join2(target, "register_callbacks.py");
|
|
16443
|
-
const targetKind = await pathKind(target);
|
|
16444
|
-
if (targetKind === "missing") {
|
|
16445
|
-
await mkdir2(plugins, { recursive: true });
|
|
16446
|
-
const staged = `${target}.tmp-${randomUUID()}`;
|
|
16447
|
-
try {
|
|
16448
|
-
await mkdir2(staged);
|
|
16449
|
-
await writeFile3(join2(staged, CODE_PUPPY_MARKER_FILE), CODE_PUPPY_MARKER);
|
|
16450
|
-
await writeFile3(join2(staged, "register_callbacks.py"), source);
|
|
16451
|
-
await rename3(staged, target);
|
|
16452
|
-
} finally {
|
|
16453
|
-
await rm2(staged, { recursive: true, force: true });
|
|
16454
|
-
}
|
|
16455
|
-
return { exitCode: 0, output: "✓ Code Puppy plugin installed" };
|
|
16456
|
-
}
|
|
16457
|
-
if (targetKind !== "directory" || await pathKind(marker) !== "file" || await readOptionalFile(marker) !== CODE_PUPPY_MARKER) {
|
|
16458
|
-
return {
|
|
16459
|
-
exitCode: 1,
|
|
16460
|
-
output: `✗ Preserved unmanaged Code Puppy plugin at ${target}`
|
|
16461
|
-
};
|
|
16462
|
-
}
|
|
16463
|
-
if (await readOptionalFile(callbacks) === source) {
|
|
16464
|
-
return { exitCode: 0, output: "✓ Code Puppy plugin is current" };
|
|
16465
|
-
}
|
|
16466
|
-
const staged = `${callbacks}.tmp-${randomUUID()}`;
|
|
16467
|
-
try {
|
|
16468
|
-
await writeFile3(staged, source);
|
|
16469
|
-
await rename3(staged, callbacks);
|
|
16470
|
-
} finally {
|
|
16471
|
-
await rm2(staged, { force: true });
|
|
16472
|
-
}
|
|
16473
|
-
return { exitCode: 0, output: "✓ Code Puppy plugin updated" };
|
|
16474
|
-
} catch (error) {
|
|
16475
|
-
return {
|
|
16476
|
-
exitCode: 1,
|
|
16477
|
-
output: `✗ Could not install Code Puppy plugin: ${getErrorMessage(error)}`
|
|
16478
|
-
};
|
|
16479
|
-
}
|
|
16480
|
-
});
|
|
16481
|
-
});
|
|
16482
|
-
async function codePuppyAdapterPath() {
|
|
16483
|
-
const candidates = [
|
|
16484
|
-
new URL("./code-puppy/register_callbacks.py", import.meta.url),
|
|
16485
|
-
new URL("../../../../code-puppy/operator/register_callbacks.py", import.meta.url)
|
|
16425
|
+
// src/commands/install/claude-code.ts
|
|
16426
|
+
var PLUGIN_ID = "operator@operator-memory";
|
|
16427
|
+
var installClaudeCode = fn2("installClaudeCode")(function* (context) {
|
|
16428
|
+
const helper = yield* runProcess("operator-helper", ["help"], context.cwd, null);
|
|
16429
|
+
const helperFailure = commandFailure(helper, "Operator Helper must be available on PATH");
|
|
16430
|
+
if (helperFailure !== null)
|
|
16431
|
+
return helperFailure;
|
|
16432
|
+
const commands = [
|
|
16433
|
+
["plugin", "marketplace", "add", "aerovato/operator-memory"],
|
|
16434
|
+
["plugin", "install", PLUGIN_ID],
|
|
16435
|
+
["plugin", "update", PLUGIN_ID]
|
|
16486
16436
|
];
|
|
16487
|
-
for (const
|
|
16488
|
-
const
|
|
16489
|
-
|
|
16490
|
-
|
|
16491
|
-
|
|
16437
|
+
for (const arguments_ of commands) {
|
|
16438
|
+
const result = yield* runProcess("claude", arguments_, context.cwd, null);
|
|
16439
|
+
const failure = commandFailure(result, "Claude Code plugin installation failed");
|
|
16440
|
+
if (failure !== null)
|
|
16441
|
+
return failure;
|
|
16442
|
+
}
|
|
16443
|
+
const status = yield* runProcess("claude", ["plugin", "list", "--json"], context.cwd, null);
|
|
16444
|
+
const statusFailure = commandFailure(status, "Could not verify Claude Code plugin installation");
|
|
16445
|
+
if (statusFailure !== null)
|
|
16446
|
+
return statusFailure;
|
|
16447
|
+
if (!(status.ok && isInstalledAndEnabled(status.stdout))) {
|
|
16448
|
+
return {
|
|
16449
|
+
exitCode: 1,
|
|
16450
|
+
output: "✗ Claude Code plugin installation finished but the plugin is not installed and enabled"
|
|
16451
|
+
};
|
|
16452
|
+
}
|
|
16453
|
+
return {
|
|
16454
|
+
exitCode: 0,
|
|
16455
|
+
output: `✓ Claude Code plugin installed and enabled
|
|
16456
|
+
Start a new Claude Code session.`
|
|
16457
|
+
};
|
|
16458
|
+
});
|
|
16459
|
+
function isInstalledAndEnabled(output) {
|
|
16460
|
+
try {
|
|
16461
|
+
const plugins = JSON.parse(output);
|
|
16462
|
+
return Array.isArray(plugins) && plugins.some((plugin) => typeof plugin === "object" && plugin !== null && ("id" in plugin) && plugin.id === PLUGIN_ID && ("enabled" in plugin) && plugin.enabled === true);
|
|
16463
|
+
} catch {
|
|
16464
|
+
return false;
|
|
16492
16465
|
}
|
|
16493
|
-
|
|
16466
|
+
}
|
|
16467
|
+
function commandFailure(result, fallback) {
|
|
16468
|
+
if (!result.ok)
|
|
16469
|
+
return { exitCode: 1, output: `✗ ${fallback}: ${result.error}` };
|
|
16470
|
+
return result.exitCode === 0 ? null : { exitCode: result.exitCode, output: result.output || fallback };
|
|
16494
16471
|
}
|
|
16495
16472
|
|
|
16496
16473
|
// src/commands/install/codex.ts
|
|
16497
|
-
import { mkdir as
|
|
16498
|
-
import { join as
|
|
16474
|
+
import { mkdir as mkdir2, writeFile as writeFile3 } from "node:fs/promises";
|
|
16475
|
+
import { join as join2 } from "node:path";
|
|
16499
16476
|
var MARKETPLACE_MARKER = `Operator Memory managed Codex marketplace.
|
|
16500
16477
|
`;
|
|
16501
16478
|
var MARKETPLACE_NAME = "operator-memory";
|
|
16502
16479
|
var PLUGIN_NAME = "aerovato";
|
|
16503
|
-
var
|
|
16480
|
+
var PLUGIN_ID2 = `${PLUGIN_NAME}@${MARKETPLACE_NAME}`;
|
|
16504
16481
|
var MARKETPLACE = `${JSON.stringify({
|
|
16505
16482
|
name: MARKETPLACE_NAME,
|
|
16506
16483
|
plugins: [
|
|
@@ -16524,20 +16501,20 @@ var installCodex = fn2("installCodex")(function* (context) {
|
|
|
16524
16501
|
if (!addMarketplace.ok && isMissingBinary(addMarketplace.error)) {
|
|
16525
16502
|
return yield* registerDesktopMarketplace(context, marketplace.path);
|
|
16526
16503
|
}
|
|
16527
|
-
const addFailure =
|
|
16504
|
+
const addFailure = commandFailure2(addMarketplace, "Codex marketplace installation failed");
|
|
16528
16505
|
if (addFailure !== null)
|
|
16529
16506
|
return addFailure;
|
|
16530
|
-
const install = yield* runProcess("codex", ["plugin", "add",
|
|
16507
|
+
const install = yield* runProcess("codex", ["plugin", "add", PLUGIN_ID2, "--json"], context.cwd, {
|
|
16531
16508
|
NPM_CONFIG_MIN_RELEASE_AGE: "0"
|
|
16532
16509
|
});
|
|
16533
|
-
const installFailure =
|
|
16510
|
+
const installFailure = commandFailure2(install, "Codex plugin installation failed");
|
|
16534
16511
|
if (installFailure !== null)
|
|
16535
16512
|
return installFailure;
|
|
16536
16513
|
const status = yield* runProcess("codex", ["plugin", "list", "--marketplace", MARKETPLACE_NAME, "--json"], context.cwd, { NPM_CONFIG_MIN_RELEASE_AGE: "0" });
|
|
16537
|
-
const statusFailure =
|
|
16514
|
+
const statusFailure = commandFailure2(status, "Could not verify Codex plugin installation");
|
|
16538
16515
|
if (statusFailure !== null)
|
|
16539
16516
|
return statusFailure;
|
|
16540
|
-
if (!(status.ok &&
|
|
16517
|
+
if (!(status.ok && isInstalledAndEnabled2(status.stdout))) {
|
|
16541
16518
|
return {
|
|
16542
16519
|
exitCode: 1,
|
|
16543
16520
|
output: "✗ Codex plugin installation finished but the plugin is not installed and enabled"
|
|
@@ -16554,9 +16531,9 @@ var installCodex = fn2("installCodex")(function* (context) {
|
|
|
16554
16531
|
});
|
|
16555
16532
|
function writeMarketplace(home) {
|
|
16556
16533
|
return promise2(async () => {
|
|
16557
|
-
const root =
|
|
16558
|
-
const marker =
|
|
16559
|
-
const marketplace =
|
|
16534
|
+
const root = join2(home, ".operator-helper", "codex-marketplace");
|
|
16535
|
+
const marker = join2(root, ".operator-managed");
|
|
16536
|
+
const marketplace = join2(root, ".agents", "plugins", "marketplace.json");
|
|
16560
16537
|
try {
|
|
16561
16538
|
const kind = await pathKind(root);
|
|
16562
16539
|
if (kind !== "missing" && (kind !== "directory" || await readOptionalFile(marker) !== MARKETPLACE_MARKER)) {
|
|
@@ -16568,9 +16545,9 @@ function writeMarketplace(home) {
|
|
|
16568
16545
|
}
|
|
16569
16546
|
};
|
|
16570
16547
|
}
|
|
16571
|
-
await
|
|
16572
|
-
await
|
|
16573
|
-
await
|
|
16548
|
+
await mkdir2(join2(root, ".agents", "plugins"), { recursive: true });
|
|
16549
|
+
await writeFile3(marker, MARKETPLACE_MARKER);
|
|
16550
|
+
await writeFile3(marketplace, MARKETPLACE);
|
|
16574
16551
|
return { ok: true, path: root };
|
|
16575
16552
|
} catch (error) {
|
|
16576
16553
|
return {
|
|
@@ -16588,12 +16565,12 @@ function isMissingBinary(error) {
|
|
|
16588
16565
|
}
|
|
16589
16566
|
function registerDesktopMarketplace(context, marketplacePath) {
|
|
16590
16567
|
return promise2(async () => {
|
|
16591
|
-
const codexHome = process.env.CODEX_HOME ||
|
|
16592
|
-
const config =
|
|
16568
|
+
const codexHome = process.env.CODEX_HOME || join2(context.home, ".codex");
|
|
16569
|
+
const config = join2(codexHome, "config.toml");
|
|
16593
16570
|
try {
|
|
16594
16571
|
const content = await readOptionalFile(config);
|
|
16595
|
-
await
|
|
16596
|
-
await
|
|
16572
|
+
await mkdir2(codexHome, { recursive: true });
|
|
16573
|
+
await writeFile3(config, upsertMarketplaceTable(content, marketplacePath));
|
|
16597
16574
|
} catch (error) {
|
|
16598
16575
|
return {
|
|
16599
16576
|
exitCode: 1,
|
|
@@ -16645,18 +16622,18 @@ ${block}`;
|
|
|
16645
16622
|
`), ...lines.slice(end)].join(`
|
|
16646
16623
|
`);
|
|
16647
16624
|
}
|
|
16648
|
-
function
|
|
16625
|
+
function isInstalledAndEnabled2(output) {
|
|
16649
16626
|
try {
|
|
16650
16627
|
const value = JSON.parse(output);
|
|
16651
16628
|
if (typeof value !== "object" || value === null || !("installed" in value))
|
|
16652
16629
|
return false;
|
|
16653
16630
|
const installed = value.installed;
|
|
16654
|
-
return Array.isArray(installed) && installed.some((plugin) => typeof plugin === "object" && plugin !== null && ("pluginId" in plugin) && plugin.pluginId ===
|
|
16631
|
+
return Array.isArray(installed) && installed.some((plugin) => typeof plugin === "object" && plugin !== null && ("pluginId" in plugin) && plugin.pluginId === PLUGIN_ID2 && ("installed" in plugin) && plugin.installed === true && ("enabled" in plugin) && plugin.enabled === true);
|
|
16655
16632
|
} catch {
|
|
16656
16633
|
return false;
|
|
16657
16634
|
}
|
|
16658
16635
|
}
|
|
16659
|
-
function
|
|
16636
|
+
function commandFailure2(result, fallback) {
|
|
16660
16637
|
if (!result.ok)
|
|
16661
16638
|
return { exitCode: 1, output: `✗ Could not run codex: ${result.error}` };
|
|
16662
16639
|
return result.exitCode === 0 ? null : { exitCode: result.exitCode, output: result.output || fallback };
|
|
@@ -16750,7 +16727,7 @@ var installPi = fn2("installPi")(function* (context) {
|
|
|
16750
16727
|
import { resolve as resolve2 } from "node:path";
|
|
16751
16728
|
|
|
16752
16729
|
// ../core/src/memory/project.ts
|
|
16753
|
-
import { extname, join as
|
|
16730
|
+
import { extname, join as join3, relative, sep } from "node:path";
|
|
16754
16731
|
|
|
16755
16732
|
// ../core/src/utils.ts
|
|
16756
16733
|
function success(value) {
|
|
@@ -16775,17 +16752,17 @@ function readString(value, key) {
|
|
|
16775
16752
|
|
|
16776
16753
|
// ../core/src/memory/common.ts
|
|
16777
16754
|
import { constants as constants2 } from "node:fs";
|
|
16778
|
-
import { access as access3, readdir, readFile as
|
|
16755
|
+
import { access as access3, readdir, readFile as readFile4, stat as stat3 } from "node:fs/promises";
|
|
16779
16756
|
async function readOptionalContent(path) {
|
|
16780
16757
|
try {
|
|
16781
|
-
return success(await
|
|
16758
|
+
return success(await readFile4(path, "utf8"));
|
|
16782
16759
|
} catch (cause) {
|
|
16783
16760
|
return hasNodeErrorCode(cause, "ENOENT") ? success(null) : failure({ path, cause });
|
|
16784
16761
|
}
|
|
16785
16762
|
}
|
|
16786
16763
|
async function readContent(path) {
|
|
16787
16764
|
try {
|
|
16788
|
-
return success(await
|
|
16765
|
+
return success(await readFile4(path, "utf8"));
|
|
16789
16766
|
} catch (cause) {
|
|
16790
16767
|
return failure({ path, cause });
|
|
16791
16768
|
}
|
|
@@ -16864,13 +16841,13 @@ async function loadProjectPartition(root, loadFiles) {
|
|
|
16864
16841
|
return hasNodeErrorCode(directory.error.cause, "ENOENT") ? success(loadFiles ? { exists: false, operatorInstructions: null, catalog: null, indexes: [] } : { exists: false }) : directory;
|
|
16865
16842
|
}
|
|
16866
16843
|
if (!loadFiles) {
|
|
16867
|
-
for (const path of [
|
|
16844
|
+
for (const path of [join3(root, "operator.md"), join3(root, "catalog.md")]) {
|
|
16868
16845
|
const file = await checkOptionalReadableFile(path);
|
|
16869
16846
|
if (!file.ok) {
|
|
16870
16847
|
return file;
|
|
16871
16848
|
}
|
|
16872
16849
|
}
|
|
16873
|
-
const indexRoot =
|
|
16850
|
+
const indexRoot = join3(root, "index");
|
|
16874
16851
|
const indexPaths = await listMarkdownFiles(indexRoot, indexRoot);
|
|
16875
16852
|
if (!indexPaths.ok) {
|
|
16876
16853
|
return indexPaths;
|
|
@@ -16883,15 +16860,15 @@ async function loadProjectPartition(root, loadFiles) {
|
|
|
16883
16860
|
}
|
|
16884
16861
|
return success({ exists: true });
|
|
16885
16862
|
}
|
|
16886
|
-
const operatorInstructions = await readOptionalContent(
|
|
16863
|
+
const operatorInstructions = await readOptionalContent(join3(root, "operator.md"));
|
|
16887
16864
|
if (!operatorInstructions.ok) {
|
|
16888
16865
|
return operatorInstructions;
|
|
16889
16866
|
}
|
|
16890
|
-
const catalog = await readOptionalContent(
|
|
16867
|
+
const catalog = await readOptionalContent(join3(root, "catalog.md"));
|
|
16891
16868
|
if (!catalog.ok) {
|
|
16892
16869
|
return catalog;
|
|
16893
16870
|
}
|
|
16894
|
-
const indexes = await readIndexes(
|
|
16871
|
+
const indexes = await readIndexes(join3(root, "index"));
|
|
16895
16872
|
if (!indexes.ok) {
|
|
16896
16873
|
return indexes;
|
|
16897
16874
|
}
|
|
@@ -16929,7 +16906,7 @@ async function listMarkdownFiles(root, directory) {
|
|
|
16929
16906
|
}
|
|
16930
16907
|
const paths = [];
|
|
16931
16908
|
for (const entry of entries.value) {
|
|
16932
|
-
const path =
|
|
16909
|
+
const path = join3(directory, entry.name);
|
|
16933
16910
|
if (entry.isDirectory()) {
|
|
16934
16911
|
const nestedPaths = await listMarkdownFiles(root, path);
|
|
16935
16912
|
if (!nestedPaths.ok) {
|
|
@@ -16950,14 +16927,14 @@ function compareStrings2(left, right) {
|
|
|
16950
16927
|
}
|
|
16951
16928
|
|
|
16952
16929
|
// ../core/src/memory/user.ts
|
|
16953
|
-
import { join as
|
|
16930
|
+
import { join as join4 } from "node:path";
|
|
16954
16931
|
async function loadUserPartition(root, loadFiles) {
|
|
16955
16932
|
if (!loadFiles) {
|
|
16956
16933
|
const directory = await readDirectory2(root);
|
|
16957
16934
|
if (!directory.ok) {
|
|
16958
16935
|
return hasNodeErrorCode(directory.error.cause, "ENOENT") ? success({ exists: false }) : directory;
|
|
16959
16936
|
}
|
|
16960
|
-
for (const path of [
|
|
16937
|
+
for (const path of [join4(root, "operator.md"), join4(root, "catalog.md")]) {
|
|
16961
16938
|
const file = await checkOptionalReadableFile(path);
|
|
16962
16939
|
if (!file.ok) {
|
|
16963
16940
|
return file;
|
|
@@ -16965,11 +16942,11 @@ async function loadUserPartition(root, loadFiles) {
|
|
|
16965
16942
|
}
|
|
16966
16943
|
return success({ exists: true });
|
|
16967
16944
|
}
|
|
16968
|
-
const operatorInstructions = await readOptionalContent(
|
|
16945
|
+
const operatorInstructions = await readOptionalContent(join4(root, "operator.md"));
|
|
16969
16946
|
if (!operatorInstructions.ok) {
|
|
16970
16947
|
return operatorInstructions;
|
|
16971
16948
|
}
|
|
16972
|
-
const catalog = await readOptionalContent(
|
|
16949
|
+
const catalog = await readOptionalContent(join4(root, "catalog.md"));
|
|
16973
16950
|
if (!catalog.ok) {
|
|
16974
16951
|
return catalog;
|
|
16975
16952
|
}
|
|
@@ -17647,103 +17624,6 @@ var describeFile = fn2("describeFile")(function* (cwd, root, file, label) {
|
|
|
17647
17624
|
];
|
|
17648
17625
|
});
|
|
17649
17626
|
|
|
17650
|
-
// src/commands/user/common.ts
|
|
17651
|
-
var USER_ROOT = "~/.operator/user";
|
|
17652
|
-
var USER_FILES = ["operator.md", "catalog.md"];
|
|
17653
|
-
|
|
17654
|
-
// src/commands/user/init.ts
|
|
17655
|
-
var userInit = fn2("userInit")((context) => gen2(function* () {
|
|
17656
|
-
const pathService = yield* Path;
|
|
17657
|
-
const root = pathService.join(context.home, ".operator", "user");
|
|
17658
|
-
yield* ensurePrivateDirectory(pathService.join(context.home, ".operator"));
|
|
17659
|
-
const templates = {
|
|
17660
|
-
"operator.md": readTemplate("user/operator.md" /* UserOperator */),
|
|
17661
|
-
"catalog.md": readTemplate("user/catalog.md" /* UserCatalog */)
|
|
17662
|
-
};
|
|
17663
|
-
const rows = [];
|
|
17664
|
-
let failed = false;
|
|
17665
|
-
for (const file of USER_FILES) {
|
|
17666
|
-
const written = yield* writeTextIfMissing(pathService.join(root, file), templates[file]).pipe(result2);
|
|
17667
|
-
if (isFailure2(written)) {
|
|
17668
|
-
failed = true;
|
|
17669
|
-
rows.push(["Failed", `${USER_ROOT}/${file}: ${fileErrorMessage(written.failure)}`]);
|
|
17670
|
-
} else {
|
|
17671
|
-
rows.push([
|
|
17672
|
-
written.success === "created" ? "Created" : "Preserved",
|
|
17673
|
-
`${USER_ROOT}/${file}`
|
|
17674
|
-
]);
|
|
17675
|
-
}
|
|
17676
|
-
}
|
|
17677
|
-
return {
|
|
17678
|
-
exitCode: failed ? 1 : 0,
|
|
17679
|
-
output: `✓ User Partition Initialized
|
|
17680
|
-
${renderTable(rows)}`
|
|
17681
|
-
};
|
|
17682
|
-
}).pipe(catch_2((error) => succeed6(fileFailure(error))), map3((result) => ({
|
|
17683
|
-
...result,
|
|
17684
|
-
output: `${result.output}
|
|
17685
|
-
|
|
17686
|
-
Follow the agent instructions below to complete User Setup.
|
|
17687
|
-
|
|
17688
|
-
${readTemplate("user/setup.md" /* UserSetup */)}`
|
|
17689
|
-
}))));
|
|
17690
|
-
|
|
17691
|
-
// src/commands/user/status.ts
|
|
17692
|
-
var INIT_HINT = [
|
|
17693
|
-
"For Users: Run `/operator:user-init` in your harness to initialize.",
|
|
17694
|
-
"For Agents: Run `operator-helper user init` and follow the guide in its output."
|
|
17695
|
-
];
|
|
17696
|
-
var userStatus = fn2("userStatus")(function* (context) {
|
|
17697
|
-
const pathService = yield* Path;
|
|
17698
|
-
const root = pathService.join(context.home, ".operator", "user");
|
|
17699
|
-
const kind = yield* inspectPath(root);
|
|
17700
|
-
if (kind === "missing") {
|
|
17701
|
-
return {
|
|
17702
|
-
exitCode: 0,
|
|
17703
|
-
output: [
|
|
17704
|
-
`User Partition: ${USER_ROOT}`,
|
|
17705
|
-
"",
|
|
17706
|
-
"⚠ User Partition Missing",
|
|
17707
|
-
renderTable([["Expected Location", `${USER_ROOT}/`]]),
|
|
17708
|
-
"",
|
|
17709
|
-
...INIT_HINT
|
|
17710
|
-
].join(`
|
|
17711
|
-
`)
|
|
17712
|
-
};
|
|
17713
|
-
}
|
|
17714
|
-
if (kind !== "directory") {
|
|
17715
|
-
return yield* new PathKindError({
|
|
17716
|
-
path: root,
|
|
17717
|
-
expected: "directory",
|
|
17718
|
-
actual: kind
|
|
17719
|
-
});
|
|
17720
|
-
}
|
|
17721
|
-
const rows = [];
|
|
17722
|
-
let missingCore = false;
|
|
17723
|
-
for (const [file, label] of [
|
|
17724
|
-
["operator.md", "User Instructions"],
|
|
17725
|
-
["catalog.md", "User Catalog"]
|
|
17726
|
-
]) {
|
|
17727
|
-
const content = yield* readOptionalText(pathService.join(root, file));
|
|
17728
|
-
if (content === null) {
|
|
17729
|
-
missingCore = true;
|
|
17730
|
-
}
|
|
17731
|
-
rows.push([
|
|
17732
|
-
label,
|
|
17733
|
-
content === null ? `Missing (${USER_ROOT}/${file})` : `${USER_ROOT}/${file} (${Buffer.byteLength(content)} bytes)`
|
|
17734
|
-
]);
|
|
17735
|
-
}
|
|
17736
|
-
const files = yield* listFiles(root);
|
|
17737
|
-
const freeform = files.filter((file) => file.relativePath !== "operator.md" && file.relativePath !== "catalog.md");
|
|
17738
|
-
rows.push(["Freeform Content", `${freeform.length} files`]);
|
|
17739
|
-
const lines = [`User Partition: ${USER_ROOT}`, "", "✓ User Partition Found", renderTable(rows)];
|
|
17740
|
-
if (missingCore) {
|
|
17741
|
-
lines.push("", ...INIT_HINT);
|
|
17742
|
-
}
|
|
17743
|
-
return { exitCode: 0, output: lines.join(`
|
|
17744
|
-
`) };
|
|
17745
|
-
});
|
|
17746
|
-
|
|
17747
17627
|
// src/update.ts
|
|
17748
17628
|
import { realpathSync } from "node:fs";
|
|
17749
17629
|
import { sep as sep2 } from "node:path";
|
|
@@ -17897,6 +17777,103 @@ var upgrade = fn2("upgrade")(function* (context) {
|
|
|
17897
17777
|
}
|
|
17898
17778
|
});
|
|
17899
17779
|
|
|
17780
|
+
// src/commands/user/common.ts
|
|
17781
|
+
var USER_ROOT = "~/.operator/user";
|
|
17782
|
+
var USER_FILES = ["operator.md", "catalog.md"];
|
|
17783
|
+
|
|
17784
|
+
// src/commands/user/init.ts
|
|
17785
|
+
var userInit = fn2("userInit")((context) => gen2(function* () {
|
|
17786
|
+
const pathService = yield* Path;
|
|
17787
|
+
const root = pathService.join(context.home, ".operator", "user");
|
|
17788
|
+
yield* ensurePrivateDirectory(pathService.join(context.home, ".operator"));
|
|
17789
|
+
const templates = {
|
|
17790
|
+
"operator.md": readTemplate("user/operator.md" /* UserOperator */),
|
|
17791
|
+
"catalog.md": readTemplate("user/catalog.md" /* UserCatalog */)
|
|
17792
|
+
};
|
|
17793
|
+
const rows = [];
|
|
17794
|
+
let failed = false;
|
|
17795
|
+
for (const file of USER_FILES) {
|
|
17796
|
+
const written = yield* writeTextIfMissing(pathService.join(root, file), templates[file]).pipe(result2);
|
|
17797
|
+
if (isFailure2(written)) {
|
|
17798
|
+
failed = true;
|
|
17799
|
+
rows.push(["Failed", `${USER_ROOT}/${file}: ${fileErrorMessage(written.failure)}`]);
|
|
17800
|
+
} else {
|
|
17801
|
+
rows.push([
|
|
17802
|
+
written.success === "created" ? "Created" : "Preserved",
|
|
17803
|
+
`${USER_ROOT}/${file}`
|
|
17804
|
+
]);
|
|
17805
|
+
}
|
|
17806
|
+
}
|
|
17807
|
+
return {
|
|
17808
|
+
exitCode: failed ? 1 : 0,
|
|
17809
|
+
output: `✓ User Partition Initialized
|
|
17810
|
+
${renderTable(rows)}`
|
|
17811
|
+
};
|
|
17812
|
+
}).pipe(catch_2((error) => succeed6(fileFailure(error))), map3((result) => ({
|
|
17813
|
+
...result,
|
|
17814
|
+
output: `${result.output}
|
|
17815
|
+
|
|
17816
|
+
Follow the agent instructions below to complete User Setup.
|
|
17817
|
+
|
|
17818
|
+
${readTemplate("user/setup.md" /* UserSetup */)}`
|
|
17819
|
+
}))));
|
|
17820
|
+
|
|
17821
|
+
// src/commands/user/status.ts
|
|
17822
|
+
var INIT_HINT = [
|
|
17823
|
+
"For Users: Run `/operator:user-init` in your harness to initialize.",
|
|
17824
|
+
"For Agents: Run `operator-helper user init` and follow the guide in its output."
|
|
17825
|
+
];
|
|
17826
|
+
var userStatus = fn2("userStatus")(function* (context) {
|
|
17827
|
+
const pathService = yield* Path;
|
|
17828
|
+
const root = pathService.join(context.home, ".operator", "user");
|
|
17829
|
+
const kind = yield* inspectPath(root);
|
|
17830
|
+
if (kind === "missing") {
|
|
17831
|
+
return {
|
|
17832
|
+
exitCode: 0,
|
|
17833
|
+
output: [
|
|
17834
|
+
`User Partition: ${USER_ROOT}`,
|
|
17835
|
+
"",
|
|
17836
|
+
"⚠ User Partition Missing",
|
|
17837
|
+
renderTable([["Expected Location", `${USER_ROOT}/`]]),
|
|
17838
|
+
"",
|
|
17839
|
+
...INIT_HINT
|
|
17840
|
+
].join(`
|
|
17841
|
+
`)
|
|
17842
|
+
};
|
|
17843
|
+
}
|
|
17844
|
+
if (kind !== "directory") {
|
|
17845
|
+
return yield* new PathKindError({
|
|
17846
|
+
path: root,
|
|
17847
|
+
expected: "directory",
|
|
17848
|
+
actual: kind
|
|
17849
|
+
});
|
|
17850
|
+
}
|
|
17851
|
+
const rows = [];
|
|
17852
|
+
let missingCore = false;
|
|
17853
|
+
for (const [file, label] of [
|
|
17854
|
+
["operator.md", "User Instructions"],
|
|
17855
|
+
["catalog.md", "User Catalog"]
|
|
17856
|
+
]) {
|
|
17857
|
+
const content = yield* readOptionalText(pathService.join(root, file));
|
|
17858
|
+
if (content === null) {
|
|
17859
|
+
missingCore = true;
|
|
17860
|
+
}
|
|
17861
|
+
rows.push([
|
|
17862
|
+
label,
|
|
17863
|
+
content === null ? `Missing (${USER_ROOT}/${file})` : `${USER_ROOT}/${file} (${Buffer.byteLength(content)} bytes)`
|
|
17864
|
+
]);
|
|
17865
|
+
}
|
|
17866
|
+
const files = yield* listFiles(root);
|
|
17867
|
+
const freeform = files.filter((file) => file.relativePath !== "operator.md" && file.relativePath !== "catalog.md");
|
|
17868
|
+
rows.push(["Freeform Content", `${freeform.length} files`]);
|
|
17869
|
+
const lines = [`User Partition: ${USER_ROOT}`, "", "✓ User Partition Found", renderTable(rows)];
|
|
17870
|
+
if (missingCore) {
|
|
17871
|
+
lines.push("", ...INIT_HINT);
|
|
17872
|
+
}
|
|
17873
|
+
return { exitCode: 0, output: lines.join(`
|
|
17874
|
+
`) };
|
|
17875
|
+
});
|
|
17876
|
+
|
|
17900
17877
|
// src/commands/version.ts
|
|
17901
17878
|
var version = fn2("version")(function* (context) {
|
|
17902
17879
|
const latest = yield* checkForUpdate(context.version).pipe(catch_2((error) => succeed6({ status: "failed", message: error.message })));
|
|
@@ -17928,7 +17905,7 @@ ${renderTable([
|
|
|
17928
17905
|
["install opencode-v2", "Install or update the OpenCode V2 plugin"],
|
|
17929
17906
|
["install deepseek", "Install or update the DeepSeek Harness plugin"],
|
|
17930
17907
|
["install pi", "Install or update the Pi plugin"],
|
|
17931
|
-
["install code
|
|
17908
|
+
["install claude-code", "Install or update the Claude Code plugin"]
|
|
17932
17909
|
])}
|
|
17933
17910
|
|
|
17934
17911
|
AGENT COMMANDS
|
|
@@ -17979,6 +17956,8 @@ function runCli(arguments_, context) {
|
|
|
17979
17956
|
return yield* memoryCheck(context);
|
|
17980
17957
|
case "install codex":
|
|
17981
17958
|
return yield* installCodex(context);
|
|
17959
|
+
case "install claude-code":
|
|
17960
|
+
return yield* installClaudeCode(context);
|
|
17982
17961
|
case "install opencode":
|
|
17983
17962
|
return yield* installOpenCode(context);
|
|
17984
17963
|
case "install opencode-v2":
|
|
@@ -17987,8 +17966,6 @@ function runCli(arguments_, context) {
|
|
|
17987
17966
|
return yield* installDeepSeek(context);
|
|
17988
17967
|
case "install pi":
|
|
17989
17968
|
return yield* installPi(context);
|
|
17990
|
-
case "install code-puppy":
|
|
17991
|
-
return yield* installCodePuppy(context);
|
|
17992
17969
|
case "user guide":
|
|
17993
17970
|
case "project guide":
|
|
17994
17971
|
case "index guide":
|
|
@@ -18027,4 +18004,4 @@ var main = gen2(function* () {
|
|
|
18027
18004
|
}).pipe(provide4(LiveLayers));
|
|
18028
18005
|
runMain2(main);
|
|
18029
18006
|
|
|
18030
|
-
//# debugId=
|
|
18007
|
+
//# debugId=FB1D99D1EABA3A2364756E2164756E21
|
package/package.json
CHANGED
|
@@ -1,407 +0,0 @@
|
|
|
1
|
-
"""Operator Memory integration for Code Puppy."""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
import asyncio
|
|
6
|
-
import importlib.metadata
|
|
7
|
-
import os
|
|
8
|
-
import subprocess
|
|
9
|
-
import threading
|
|
10
|
-
from collections.abc import AsyncGenerator
|
|
11
|
-
from contextlib import asynccontextmanager, suppress
|
|
12
|
-
from typing import Any
|
|
13
|
-
|
|
14
|
-
from code_puppy.callbacks import register_callback
|
|
15
|
-
from code_puppy.config import get_current_session_name
|
|
16
|
-
from code_puppy.messaging import emit_warning
|
|
17
|
-
from code_puppy.tools.subagent_context import get_conversation_root_id
|
|
18
|
-
from pydantic_ai import ModelRequest, RunContext, UserPromptPart
|
|
19
|
-
from pydantic_ai.messages import ModelMessage, ModelResponse
|
|
20
|
-
from pydantic_ai.models import (
|
|
21
|
-
KnownModelName,
|
|
22
|
-
Model,
|
|
23
|
-
ModelRequestParameters,
|
|
24
|
-
StreamedResponse,
|
|
25
|
-
)
|
|
26
|
-
from pydantic_ai.models.wrapper import WrapperModel
|
|
27
|
-
from pydantic_ai.settings import ModelSettings
|
|
28
|
-
|
|
29
|
-
_MARKER_KEY = "operator_memory"
|
|
30
|
-
_MARKER_VALUE = "synthetic-preamble-v1"
|
|
31
|
-
_MODEL_MESSAGE_TRANSFORM_VERSION = (0, 0, 753)
|
|
32
|
-
|
|
33
|
-
try:
|
|
34
|
-
from code_puppy.callbacks import CustomCommandResult
|
|
35
|
-
except ImportError:
|
|
36
|
-
try:
|
|
37
|
-
# Code Puppy < 0.0.698 exposes the same contract in the
|
|
38
|
-
# customizable-commands plugin.
|
|
39
|
-
from code_puppy.plugins.customizable_commands.register_callbacks import (
|
|
40
|
-
MarkdownCommandResult as CustomCommandResult,
|
|
41
|
-
)
|
|
42
|
-
except ImportError:
|
|
43
|
-
CustomCommandResult = None
|
|
44
|
-
|
|
45
|
-
_RENDER_TIMEOUT_SECONDS = 30
|
|
46
|
-
_render_tasks: dict[str, asyncio.Task[str]] = {}
|
|
47
|
-
_update_launched = False
|
|
48
|
-
|
|
49
|
-
_COMMANDS = {
|
|
50
|
-
"operator:user-init": (
|
|
51
|
-
"Initialize Operator User Instructions",
|
|
52
|
-
"user init",
|
|
53
|
-
"Follow the User Setup guide in its output, including when initialization reports failures.",
|
|
54
|
-
),
|
|
55
|
-
"operator:project-init": (
|
|
56
|
-
"Initialize Operator Project",
|
|
57
|
-
"project init",
|
|
58
|
-
"Follow the Project Setup guide in its output, including when initialization reports failures.",
|
|
59
|
-
),
|
|
60
|
-
"operator:index": (
|
|
61
|
-
"Build or refresh the Operator Project Index",
|
|
62
|
-
"index init",
|
|
63
|
-
"Follow the Project Index Setup guide in its output, including when inspection reports failures.",
|
|
64
|
-
),
|
|
65
|
-
"operator:repair": (
|
|
66
|
-
"Repair Operator",
|
|
67
|
-
"memory check",
|
|
68
|
-
"If no issues are detected, stop. Otherwise, repair only the reported load "
|
|
69
|
-
"failures without initializing absent partitions. Rerun `operator-helper memory "
|
|
70
|
-
"check` to confirm the repair, then read the applicable Operator memory documents.",
|
|
71
|
-
),
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
class OperatorModel(WrapperModel):
|
|
76
|
-
"""Inject an immutable Operator preamble into final model requests."""
|
|
77
|
-
|
|
78
|
-
def __init__(self, wrapped: Model[Any] | KnownModelName, conversation_key: str):
|
|
79
|
-
super().__init__(wrapped)
|
|
80
|
-
self._conversation_key = conversation_key
|
|
81
|
-
|
|
82
|
-
async def request(
|
|
83
|
-
self,
|
|
84
|
-
messages: list[ModelMessage],
|
|
85
|
-
model_settings: ModelSettings | None,
|
|
86
|
-
model_request_parameters: ModelRequestParameters,
|
|
87
|
-
) -> ModelResponse:
|
|
88
|
-
transformed = await _inject_preamble(messages, self._conversation_key)
|
|
89
|
-
return await self.wrapped.request(
|
|
90
|
-
transformed, model_settings, model_request_parameters
|
|
91
|
-
)
|
|
92
|
-
|
|
93
|
-
@asynccontextmanager
|
|
94
|
-
async def request_stream(
|
|
95
|
-
self,
|
|
96
|
-
messages: list[ModelMessage],
|
|
97
|
-
model_settings: ModelSettings | None,
|
|
98
|
-
model_request_parameters: ModelRequestParameters,
|
|
99
|
-
run_context: RunContext[Any] | None = None,
|
|
100
|
-
) -> AsyncGenerator[StreamedResponse]:
|
|
101
|
-
transformed = await _inject_preamble(messages, self._conversation_key)
|
|
102
|
-
async with self.wrapped.request_stream(
|
|
103
|
-
transformed, model_settings, model_request_parameters, run_context
|
|
104
|
-
) as response:
|
|
105
|
-
yield response
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
async def _inject_preamble(
|
|
109
|
-
messages: list[ModelMessage], conversation_key: str
|
|
110
|
-
) -> list[ModelMessage]:
|
|
111
|
-
preamble = await _get_preamble(conversation_key)
|
|
112
|
-
transformed = [message for message in messages if not _is_operator_request(message)]
|
|
113
|
-
transformed.insert(
|
|
114
|
-
0,
|
|
115
|
-
ModelRequest(
|
|
116
|
-
parts=[UserPromptPart(content=preamble)],
|
|
117
|
-
metadata={_MARKER_KEY: _MARKER_VALUE},
|
|
118
|
-
),
|
|
119
|
-
)
|
|
120
|
-
return transformed
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
async def _transform_model_messages(
|
|
124
|
-
_agent_name: str | None, messages: list[ModelMessage]
|
|
125
|
-
) -> None:
|
|
126
|
-
messages[:] = await _inject_preamble(messages, _conversation_key())
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
def _is_operator_request(message: ModelMessage) -> bool:
|
|
130
|
-
return (
|
|
131
|
-
isinstance(message, ModelRequest)
|
|
132
|
-
and message.metadata is not None
|
|
133
|
-
and message.metadata.get(_MARKER_KEY) == _MARKER_VALUE
|
|
134
|
-
)
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
async def _get_preamble(conversation_key: str) -> str:
|
|
138
|
-
task = _render_tasks.get(conversation_key)
|
|
139
|
-
if task is None:
|
|
140
|
-
task = asyncio.create_task(_render_preamble(conversation_key))
|
|
141
|
-
_render_tasks[conversation_key] = task
|
|
142
|
-
return await asyncio.shield(task)
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
async def _render_preamble(conversation_key: str) -> str:
|
|
146
|
-
process: asyncio.subprocess.Process | None = None
|
|
147
|
-
try:
|
|
148
|
-
process = await asyncio.create_subprocess_exec(
|
|
149
|
-
"operator-helper",
|
|
150
|
-
"preamble",
|
|
151
|
-
stdout=asyncio.subprocess.PIPE,
|
|
152
|
-
stderr=asyncio.subprocess.PIPE,
|
|
153
|
-
)
|
|
154
|
-
stdout, stderr = await asyncio.wait_for(
|
|
155
|
-
process.communicate(), timeout=_RENDER_TIMEOUT_SECONDS
|
|
156
|
-
)
|
|
157
|
-
content = _remove_process_newline(stdout.decode(errors="replace"))
|
|
158
|
-
if process.returncode == 0 and content:
|
|
159
|
-
return content
|
|
160
|
-
detail = _remove_process_newline(stderr.decode(errors="replace"))
|
|
161
|
-
reason = detail or f"operator-helper exited with status {process.returncode}"
|
|
162
|
-
except asyncio.CancelledError:
|
|
163
|
-
if process is not None:
|
|
164
|
-
await _stop_process(process)
|
|
165
|
-
raise
|
|
166
|
-
except TimeoutError:
|
|
167
|
-
if process is not None:
|
|
168
|
-
await _stop_process(process)
|
|
169
|
-
reason = "operator-helper preamble timed out"
|
|
170
|
-
except OSError as error:
|
|
171
|
-
if process is not None:
|
|
172
|
-
await _stop_process(process)
|
|
173
|
-
reason = str(error)
|
|
174
|
-
|
|
175
|
-
emit_warning(
|
|
176
|
-
"Operator Memory could not render for this conversation. "
|
|
177
|
-
"Run `operator-helper install code-puppy` to repair the integration."
|
|
178
|
-
)
|
|
179
|
-
return (
|
|
180
|
-
"<operator-diagnostic>\n"
|
|
181
|
-
"Operator Memory could not render its preamble. Continue without Operator Memory, "
|
|
182
|
-
"help the user repair `operator-helper`, then ask them to start a new conversation.\n"
|
|
183
|
-
f"Failure: {reason}\n"
|
|
184
|
-
"</operator-diagnostic>"
|
|
185
|
-
)
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
async def _stop_process(process: asyncio.subprocess.Process) -> None:
|
|
189
|
-
with suppress(ProcessLookupError):
|
|
190
|
-
process.kill()
|
|
191
|
-
await process.wait()
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
def _remove_process_newline(value: str) -> str:
|
|
195
|
-
if value.endswith("\r\n"):
|
|
196
|
-
return value[:-2]
|
|
197
|
-
if value.endswith("\n"):
|
|
198
|
-
return value[:-1]
|
|
199
|
-
return value
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
def _conversation_key() -> str:
|
|
203
|
-
return get_conversation_root_id() or get_current_session_name()
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
def _installed_code_puppy_version() -> str | None:
|
|
207
|
-
try:
|
|
208
|
-
return importlib.metadata.version("code-puppy")
|
|
209
|
-
except Exception:
|
|
210
|
-
return None
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
def _supports_model_message_transform(version: str | None) -> bool:
|
|
214
|
-
if version is None:
|
|
215
|
-
return False
|
|
216
|
-
release = version.partition("+")[0].partition("-")[0]
|
|
217
|
-
try:
|
|
218
|
-
parts = tuple(int(part) for part in release.split("."))
|
|
219
|
-
except ValueError:
|
|
220
|
-
return False
|
|
221
|
-
return parts >= _MODEL_MESSAGE_TRANSFORM_VERSION
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
def _register_preamble_injection(version: str | None) -> None:
|
|
225
|
-
if _supports_model_message_transform(version):
|
|
226
|
-
register_callback("transform_model_messages", _transform_model_messages)
|
|
227
|
-
else:
|
|
228
|
-
register_callback("agent_run_context", _agent_run_context)
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
@asynccontextmanager
|
|
232
|
-
async def _agent_run_context(
|
|
233
|
-
_agent: Any, pydantic_agent: Any, _group_id: str, _mcp_servers: Any
|
|
234
|
-
) -> AsyncGenerator[None]:
|
|
235
|
-
model = pydantic_agent.model
|
|
236
|
-
if model is None:
|
|
237
|
-
yield
|
|
238
|
-
return
|
|
239
|
-
if _is_dbos_agent(pydantic_agent):
|
|
240
|
-
_warn_dbos_incompatible()
|
|
241
|
-
yield
|
|
242
|
-
return
|
|
243
|
-
with pydantic_agent.override(model=OperatorModel(model, _conversation_key())):
|
|
244
|
-
yield
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
def _is_dbos_agent(pydantic_agent: Any) -> bool:
|
|
248
|
-
# DBOSAgent installs its own captured DBOSModel through a nested
|
|
249
|
-
# override inside run(), which supersedes any override applied here.
|
|
250
|
-
# Duck-type: avoid importing the optional durable_exec module.
|
|
251
|
-
return hasattr(pydantic_agent, "_dbos_overrides")
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
_DBOS_WARNED = False
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
def _warn_dbos_incompatible() -> None:
|
|
258
|
-
global _DBOS_WARNED
|
|
259
|
-
if _DBOS_WARNED:
|
|
260
|
-
return
|
|
261
|
-
_DBOS_WARNED = True
|
|
262
|
-
emit_warning(
|
|
263
|
-
"Operator Memory preamble injection is disabled: DBOS durable execution "
|
|
264
|
-
"supersedes the model override it relies on. Disable DBOS to restore "
|
|
265
|
-
"Operator Memory: /set enable_dbos false, then restart Code Puppy."
|
|
266
|
-
)
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
async def _session_end() -> None:
|
|
270
|
-
tasks = tuple(_render_tasks.values())
|
|
271
|
-
_render_tasks.clear()
|
|
272
|
-
for task in tasks:
|
|
273
|
-
task.cancel()
|
|
274
|
-
await asyncio.gather(*tasks, return_exceptions=True)
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
def _custom_command(_command: str, name: str) -> CustomCommandResult | None:
|
|
278
|
-
definition = _COMMANDS.get(name)
|
|
279
|
-
if definition is None:
|
|
280
|
-
return None
|
|
281
|
-
|
|
282
|
-
description, operation, instructions = definition
|
|
283
|
-
return CustomCommandResult(
|
|
284
|
-
f"# {description}\n\n"
|
|
285
|
-
"1. Run `operator-helper version`. If an update is available, run "
|
|
286
|
-
"`operator-helper upgrade` before continuing.\n"
|
|
287
|
-
f"2. Run `operator-helper {operation}`. {instructions}\n\n"
|
|
288
|
-
"## Recovery\n\n"
|
|
289
|
-
"- If Helper cannot start, install or repair the npm package "
|
|
290
|
-
"`@aerovato/operator-helper` globally and retry the failed command.\n"
|
|
291
|
-
"- If the version check or upgrade fails, diagnose the error and retry.\n"
|
|
292
|
-
f"- If `operator-helper {operation}` reports a failure, use its output to "
|
|
293
|
-
"resolve it and rerun it as needed.\n"
|
|
294
|
-
"- If you cannot resolve a problem, report the blocker.\n\n"
|
|
295
|
-
"Use Helper output as working context. Do not reproduce it wholesale or "
|
|
296
|
-
"reimplement Helper logic."
|
|
297
|
-
)
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
def _custom_command_help() -> list[tuple[str, str]]:
|
|
301
|
-
return [(name, definition[0]) for name, definition in _COMMANDS.items()]
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
_UPDATE_NOTE = "Operator Updated · Restart Code Puppy to apply"
|
|
305
|
-
_update_note_pending = False
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
def _launch_update() -> None:
|
|
309
|
-
global _update_launched
|
|
310
|
-
if _update_launched:
|
|
311
|
-
return
|
|
312
|
-
_update_launched = True
|
|
313
|
-
try:
|
|
314
|
-
process = subprocess.Popen(
|
|
315
|
-
("operator-helper", "install", "code-puppy"),
|
|
316
|
-
stdin=subprocess.DEVNULL,
|
|
317
|
-
stdout=subprocess.PIPE,
|
|
318
|
-
stderr=subprocess.DEVNULL,
|
|
319
|
-
text=True,
|
|
320
|
-
creationflags=(
|
|
321
|
-
subprocess.CREATE_NEW_PROCESS_GROUP | subprocess.DETACHED_PROCESS
|
|
322
|
-
if os.name == "nt"
|
|
323
|
-
else 0
|
|
324
|
-
),
|
|
325
|
-
start_new_session=os.name != "nt",
|
|
326
|
-
)
|
|
327
|
-
except OSError:
|
|
328
|
-
return
|
|
329
|
-
threading.Thread(target=_monitor_update, args=(process,), daemon=True).start()
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
def _monitor_update(process: subprocess.Popen[str]) -> None:
|
|
333
|
-
global _update_note_pending
|
|
334
|
-
try:
|
|
335
|
-
output = process.communicate()[0] or ""
|
|
336
|
-
except Exception:
|
|
337
|
-
return
|
|
338
|
-
if process.returncode == 0 and "plugin updated" in output:
|
|
339
|
-
_update_note_pending = True
|
|
340
|
-
_paint_status_indicator(_UPDATE_NOTE)
|
|
341
|
-
_emit_update_notice()
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
def _emit_update_notice() -> None:
|
|
345
|
-
try:
|
|
346
|
-
from rich.text import Text
|
|
347
|
-
|
|
348
|
-
from code_puppy.messaging import emit_warning
|
|
349
|
-
|
|
350
|
-
notice = Text()
|
|
351
|
-
notice.append("Operator Updated · ", style="bold magenta")
|
|
352
|
-
notice.append("Restart Code Puppy to apply", style="magenta")
|
|
353
|
-
emit_warning(notice)
|
|
354
|
-
except Exception:
|
|
355
|
-
pass
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
_STARTUP_INDICATOR = "Operator Ready"
|
|
359
|
-
_STATUS_INDICATOR = " Operator Ready"
|
|
360
|
-
_active_runs = 0
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
def _paint_status_indicator(text: str) -> None:
|
|
364
|
-
try:
|
|
365
|
-
from code_puppy.messaging.bottom_bar import get_bottom_bar
|
|
366
|
-
|
|
367
|
-
get_bottom_bar().set_status_suffix(text)
|
|
368
|
-
except Exception:
|
|
369
|
-
pass
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
async def _show_status_indicator() -> None:
|
|
373
|
-
global _update_note_pending
|
|
374
|
-
if _update_note_pending:
|
|
375
|
-
_update_note_pending = False
|
|
376
|
-
return
|
|
377
|
-
_paint_status_indicator(_STARTUP_INDICATOR)
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
async def _agent_run_start(*_args: Any) -> None:
|
|
381
|
-
global _active_runs
|
|
382
|
-
_active_runs += 1
|
|
383
|
-
_paint_status_indicator(_STATUS_INDICATOR)
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
async def _agent_run_end(*_args: Any) -> None:
|
|
387
|
-
global _active_runs
|
|
388
|
-
_active_runs = max(0, _active_runs - 1)
|
|
389
|
-
if _active_runs == 0:
|
|
390
|
-
_paint_status_indicator("")
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
_register_preamble_injection(_installed_code_puppy_version())
|
|
394
|
-
register_callback("session_end", _session_end)
|
|
395
|
-
if CustomCommandResult is not None:
|
|
396
|
-
register_callback("custom_command", _custom_command)
|
|
397
|
-
register_callback("custom_command_help", _custom_command_help)
|
|
398
|
-
else:
|
|
399
|
-
emit_warning(
|
|
400
|
-
"Operator commands are disabled: this Code Puppy version is too old to "
|
|
401
|
-
"process command output as user input. Preamble injection still works. "
|
|
402
|
-
"Upgrade Code Puppy to enable the /operator:* commands."
|
|
403
|
-
)
|
|
404
|
-
register_callback("startup", _show_status_indicator)
|
|
405
|
-
register_callback("agent_run_start", _agent_run_start)
|
|
406
|
-
register_callback("agent_run_end", _agent_run_end)
|
|
407
|
-
_launch_update()
|