@aerovato/operator-helper 1.6.1 → 1.7.1
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 +253 -273
- 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.1",
|
|
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 = {
|
|
@@ -15811,23 +15876,23 @@ npm install --global @aerovato/operator-helper
|
|
|
15811
15876
|
# With Bun
|
|
15812
15877
|
bun add --global --minimum-release-age 0 @aerovato/operator-helper@latest
|
|
15813
15878
|
|
|
15814
|
-
# Install
|
|
15815
|
-
operator-helper install
|
|
15879
|
+
# Install Claude Code plugin
|
|
15880
|
+
operator-helper install claude-code
|
|
15881
|
+
|
|
15882
|
+
# Install Codex plugin
|
|
15883
|
+
operator-helper install codex
|
|
15816
15884
|
|
|
15817
15885
|
# Install OpenCode V2 plugin
|
|
15818
15886
|
operator-helper install opencode-v2
|
|
15819
15887
|
|
|
15820
|
-
# Install
|
|
15821
|
-
operator-helper install
|
|
15888
|
+
# Install OpenCode plugin
|
|
15889
|
+
operator-helper install opencode
|
|
15822
15890
|
|
|
15823
15891
|
# Install Pi plugin
|
|
15824
15892
|
operator-helper install pi
|
|
15825
15893
|
|
|
15826
15894
|
# Install DeepSeek Harness plugin
|
|
15827
15895
|
operator-helper install deepseek
|
|
15828
|
-
|
|
15829
|
-
# Install Code Puppy plugin
|
|
15830
|
-
operator-helper install code-puppy
|
|
15831
15896
|
\`\`\`
|
|
15832
15897
|
`;
|
|
15833
15898
|
|
|
@@ -16153,70 +16218,6 @@ function readTemplate(path) {
|
|
|
16153
16218
|
return TEMPLATES[path];
|
|
16154
16219
|
}
|
|
16155
16220
|
|
|
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
16221
|
// src/commands/index/init.ts
|
|
16221
16222
|
var PARTITION_ROOTS = [".operator-shared", ".operator"];
|
|
16222
16223
|
var indexInit = fn2("indexInit")((context) => gen2(function* () {
|
|
@@ -16424,83 +16425,62 @@ var indexLint = fn2("indexLint")(function* (context) {
|
|
|
16424
16425
|
};
|
|
16425
16426
|
});
|
|
16426
16427
|
|
|
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)
|
|
16428
|
+
// src/commands/install/claude-code.ts
|
|
16429
|
+
var PLUGIN_ID = "operator@operator-memory";
|
|
16430
|
+
var installClaudeCode = fn2("installClaudeCode")(function* (context) {
|
|
16431
|
+
const helper = yield* runProcess("operator-helper", ["help"], context.cwd, null);
|
|
16432
|
+
const helperFailure = commandFailure(helper, "Operator Helper must be available on PATH");
|
|
16433
|
+
if (helperFailure !== null)
|
|
16434
|
+
return helperFailure;
|
|
16435
|
+
const commands = [
|
|
16436
|
+
["plugin", "marketplace", "add", "aerovato/operator-memory"],
|
|
16437
|
+
["plugin", "install", PLUGIN_ID],
|
|
16438
|
+
["plugin", "update", PLUGIN_ID]
|
|
16486
16439
|
];
|
|
16487
|
-
for (const
|
|
16488
|
-
const
|
|
16489
|
-
|
|
16490
|
-
|
|
16491
|
-
|
|
16440
|
+
for (const arguments_ of commands) {
|
|
16441
|
+
const result = yield* runProcess("claude", arguments_, context.cwd, null);
|
|
16442
|
+
const failure = commandFailure(result, "Claude Code plugin installation failed");
|
|
16443
|
+
if (failure !== null)
|
|
16444
|
+
return failure;
|
|
16445
|
+
}
|
|
16446
|
+
const status = yield* runProcess("claude", ["plugin", "list", "--json"], context.cwd, null);
|
|
16447
|
+
const statusFailure = commandFailure(status, "Could not verify Claude Code plugin installation");
|
|
16448
|
+
if (statusFailure !== null)
|
|
16449
|
+
return statusFailure;
|
|
16450
|
+
if (!(status.ok && isInstalledAndEnabled(status.stdout))) {
|
|
16451
|
+
return {
|
|
16452
|
+
exitCode: 1,
|
|
16453
|
+
output: "✗ Claude Code plugin installation finished but the plugin is not installed and enabled"
|
|
16454
|
+
};
|
|
16455
|
+
}
|
|
16456
|
+
return {
|
|
16457
|
+
exitCode: 0,
|
|
16458
|
+
output: `✓ Claude Code plugin installed and enabled
|
|
16459
|
+
Start a new Claude Code session.`
|
|
16460
|
+
};
|
|
16461
|
+
});
|
|
16462
|
+
function isInstalledAndEnabled(output) {
|
|
16463
|
+
try {
|
|
16464
|
+
const plugins = JSON.parse(output);
|
|
16465
|
+
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);
|
|
16466
|
+
} catch {
|
|
16467
|
+
return false;
|
|
16492
16468
|
}
|
|
16493
|
-
|
|
16469
|
+
}
|
|
16470
|
+
function commandFailure(result, fallback) {
|
|
16471
|
+
if (!result.ok)
|
|
16472
|
+
return { exitCode: 1, output: `✗ ${fallback}: ${result.error}` };
|
|
16473
|
+
return result.exitCode === 0 ? null : { exitCode: result.exitCode, output: result.output || fallback };
|
|
16494
16474
|
}
|
|
16495
16475
|
|
|
16496
16476
|
// src/commands/install/codex.ts
|
|
16497
|
-
import { mkdir as
|
|
16498
|
-
import { join as
|
|
16477
|
+
import { mkdir as mkdir2, writeFile as writeFile3 } from "node:fs/promises";
|
|
16478
|
+
import { join as join2 } from "node:path";
|
|
16499
16479
|
var MARKETPLACE_MARKER = `Operator Memory managed Codex marketplace.
|
|
16500
16480
|
`;
|
|
16501
16481
|
var MARKETPLACE_NAME = "operator-memory";
|
|
16502
16482
|
var PLUGIN_NAME = "aerovato";
|
|
16503
|
-
var
|
|
16483
|
+
var PLUGIN_ID2 = `${PLUGIN_NAME}@${MARKETPLACE_NAME}`;
|
|
16504
16484
|
var MARKETPLACE = `${JSON.stringify({
|
|
16505
16485
|
name: MARKETPLACE_NAME,
|
|
16506
16486
|
plugins: [
|
|
@@ -16524,20 +16504,20 @@ var installCodex = fn2("installCodex")(function* (context) {
|
|
|
16524
16504
|
if (!addMarketplace.ok && isMissingBinary(addMarketplace.error)) {
|
|
16525
16505
|
return yield* registerDesktopMarketplace(context, marketplace.path);
|
|
16526
16506
|
}
|
|
16527
|
-
const addFailure =
|
|
16507
|
+
const addFailure = commandFailure2(addMarketplace, "Codex marketplace installation failed");
|
|
16528
16508
|
if (addFailure !== null)
|
|
16529
16509
|
return addFailure;
|
|
16530
|
-
const install = yield* runProcess("codex", ["plugin", "add",
|
|
16510
|
+
const install = yield* runProcess("codex", ["plugin", "add", PLUGIN_ID2, "--json"], context.cwd, {
|
|
16531
16511
|
NPM_CONFIG_MIN_RELEASE_AGE: "0"
|
|
16532
16512
|
});
|
|
16533
|
-
const installFailure =
|
|
16513
|
+
const installFailure = commandFailure2(install, "Codex plugin installation failed");
|
|
16534
16514
|
if (installFailure !== null)
|
|
16535
16515
|
return installFailure;
|
|
16536
16516
|
const status = yield* runProcess("codex", ["plugin", "list", "--marketplace", MARKETPLACE_NAME, "--json"], context.cwd, { NPM_CONFIG_MIN_RELEASE_AGE: "0" });
|
|
16537
|
-
const statusFailure =
|
|
16517
|
+
const statusFailure = commandFailure2(status, "Could not verify Codex plugin installation");
|
|
16538
16518
|
if (statusFailure !== null)
|
|
16539
16519
|
return statusFailure;
|
|
16540
|
-
if (!(status.ok &&
|
|
16520
|
+
if (!(status.ok && isInstalledAndEnabled2(status.stdout))) {
|
|
16541
16521
|
return {
|
|
16542
16522
|
exitCode: 1,
|
|
16543
16523
|
output: "✗ Codex plugin installation finished but the plugin is not installed and enabled"
|
|
@@ -16554,9 +16534,9 @@ var installCodex = fn2("installCodex")(function* (context) {
|
|
|
16554
16534
|
});
|
|
16555
16535
|
function writeMarketplace(home) {
|
|
16556
16536
|
return promise2(async () => {
|
|
16557
|
-
const root =
|
|
16558
|
-
const marker =
|
|
16559
|
-
const marketplace =
|
|
16537
|
+
const root = join2(home, ".operator-helper", "codex-marketplace");
|
|
16538
|
+
const marker = join2(root, ".operator-managed");
|
|
16539
|
+
const marketplace = join2(root, ".agents", "plugins", "marketplace.json");
|
|
16560
16540
|
try {
|
|
16561
16541
|
const kind = await pathKind(root);
|
|
16562
16542
|
if (kind !== "missing" && (kind !== "directory" || await readOptionalFile(marker) !== MARKETPLACE_MARKER)) {
|
|
@@ -16568,9 +16548,9 @@ function writeMarketplace(home) {
|
|
|
16568
16548
|
}
|
|
16569
16549
|
};
|
|
16570
16550
|
}
|
|
16571
|
-
await
|
|
16572
|
-
await
|
|
16573
|
-
await
|
|
16551
|
+
await mkdir2(join2(root, ".agents", "plugins"), { recursive: true });
|
|
16552
|
+
await writeFile3(marker, MARKETPLACE_MARKER);
|
|
16553
|
+
await writeFile3(marketplace, MARKETPLACE);
|
|
16574
16554
|
return { ok: true, path: root };
|
|
16575
16555
|
} catch (error) {
|
|
16576
16556
|
return {
|
|
@@ -16588,12 +16568,12 @@ function isMissingBinary(error) {
|
|
|
16588
16568
|
}
|
|
16589
16569
|
function registerDesktopMarketplace(context, marketplacePath) {
|
|
16590
16570
|
return promise2(async () => {
|
|
16591
|
-
const codexHome = process.env.CODEX_HOME ||
|
|
16592
|
-
const config =
|
|
16571
|
+
const codexHome = process.env.CODEX_HOME || join2(context.home, ".codex");
|
|
16572
|
+
const config = join2(codexHome, "config.toml");
|
|
16593
16573
|
try {
|
|
16594
16574
|
const content = await readOptionalFile(config);
|
|
16595
|
-
await
|
|
16596
|
-
await
|
|
16575
|
+
await mkdir2(codexHome, { recursive: true });
|
|
16576
|
+
await writeFile3(config, upsertMarketplaceTable(content, marketplacePath));
|
|
16597
16577
|
} catch (error) {
|
|
16598
16578
|
return {
|
|
16599
16579
|
exitCode: 1,
|
|
@@ -16645,18 +16625,18 @@ ${block}`;
|
|
|
16645
16625
|
`), ...lines.slice(end)].join(`
|
|
16646
16626
|
`);
|
|
16647
16627
|
}
|
|
16648
|
-
function
|
|
16628
|
+
function isInstalledAndEnabled2(output) {
|
|
16649
16629
|
try {
|
|
16650
16630
|
const value = JSON.parse(output);
|
|
16651
16631
|
if (typeof value !== "object" || value === null || !("installed" in value))
|
|
16652
16632
|
return false;
|
|
16653
16633
|
const installed = value.installed;
|
|
16654
|
-
return Array.isArray(installed) && installed.some((plugin) => typeof plugin === "object" && plugin !== null && ("pluginId" in plugin) && plugin.pluginId ===
|
|
16634
|
+
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
16635
|
} catch {
|
|
16656
16636
|
return false;
|
|
16657
16637
|
}
|
|
16658
16638
|
}
|
|
16659
|
-
function
|
|
16639
|
+
function commandFailure2(result, fallback) {
|
|
16660
16640
|
if (!result.ok)
|
|
16661
16641
|
return { exitCode: 1, output: `✗ Could not run codex: ${result.error}` };
|
|
16662
16642
|
return result.exitCode === 0 ? null : { exitCode: result.exitCode, output: result.output || fallback };
|
|
@@ -16750,7 +16730,7 @@ var installPi = fn2("installPi")(function* (context) {
|
|
|
16750
16730
|
import { resolve as resolve2 } from "node:path";
|
|
16751
16731
|
|
|
16752
16732
|
// ../core/src/memory/project.ts
|
|
16753
|
-
import { extname, join as
|
|
16733
|
+
import { extname, join as join3, relative, sep } from "node:path";
|
|
16754
16734
|
|
|
16755
16735
|
// ../core/src/utils.ts
|
|
16756
16736
|
function success(value) {
|
|
@@ -16775,17 +16755,17 @@ function readString(value, key) {
|
|
|
16775
16755
|
|
|
16776
16756
|
// ../core/src/memory/common.ts
|
|
16777
16757
|
import { constants as constants2 } from "node:fs";
|
|
16778
|
-
import { access as access3, readdir, readFile as
|
|
16758
|
+
import { access as access3, readdir, readFile as readFile4, stat as stat3 } from "node:fs/promises";
|
|
16779
16759
|
async function readOptionalContent(path) {
|
|
16780
16760
|
try {
|
|
16781
|
-
return success(await
|
|
16761
|
+
return success(await readFile4(path, "utf8"));
|
|
16782
16762
|
} catch (cause) {
|
|
16783
16763
|
return hasNodeErrorCode(cause, "ENOENT") ? success(null) : failure({ path, cause });
|
|
16784
16764
|
}
|
|
16785
16765
|
}
|
|
16786
16766
|
async function readContent(path) {
|
|
16787
16767
|
try {
|
|
16788
|
-
return success(await
|
|
16768
|
+
return success(await readFile4(path, "utf8"));
|
|
16789
16769
|
} catch (cause) {
|
|
16790
16770
|
return failure({ path, cause });
|
|
16791
16771
|
}
|
|
@@ -16864,13 +16844,13 @@ async function loadProjectPartition(root, loadFiles) {
|
|
|
16864
16844
|
return hasNodeErrorCode(directory.error.cause, "ENOENT") ? success(loadFiles ? { exists: false, operatorInstructions: null, catalog: null, indexes: [] } : { exists: false }) : directory;
|
|
16865
16845
|
}
|
|
16866
16846
|
if (!loadFiles) {
|
|
16867
|
-
for (const path of [
|
|
16847
|
+
for (const path of [join3(root, "operator.md"), join3(root, "catalog.md")]) {
|
|
16868
16848
|
const file = await checkOptionalReadableFile(path);
|
|
16869
16849
|
if (!file.ok) {
|
|
16870
16850
|
return file;
|
|
16871
16851
|
}
|
|
16872
16852
|
}
|
|
16873
|
-
const indexRoot =
|
|
16853
|
+
const indexRoot = join3(root, "index");
|
|
16874
16854
|
const indexPaths = await listMarkdownFiles(indexRoot, indexRoot);
|
|
16875
16855
|
if (!indexPaths.ok) {
|
|
16876
16856
|
return indexPaths;
|
|
@@ -16883,15 +16863,15 @@ async function loadProjectPartition(root, loadFiles) {
|
|
|
16883
16863
|
}
|
|
16884
16864
|
return success({ exists: true });
|
|
16885
16865
|
}
|
|
16886
|
-
const operatorInstructions = await readOptionalContent(
|
|
16866
|
+
const operatorInstructions = await readOptionalContent(join3(root, "operator.md"));
|
|
16887
16867
|
if (!operatorInstructions.ok) {
|
|
16888
16868
|
return operatorInstructions;
|
|
16889
16869
|
}
|
|
16890
|
-
const catalog = await readOptionalContent(
|
|
16870
|
+
const catalog = await readOptionalContent(join3(root, "catalog.md"));
|
|
16891
16871
|
if (!catalog.ok) {
|
|
16892
16872
|
return catalog;
|
|
16893
16873
|
}
|
|
16894
|
-
const indexes = await readIndexes(
|
|
16874
|
+
const indexes = await readIndexes(join3(root, "index"));
|
|
16895
16875
|
if (!indexes.ok) {
|
|
16896
16876
|
return indexes;
|
|
16897
16877
|
}
|
|
@@ -16929,7 +16909,7 @@ async function listMarkdownFiles(root, directory) {
|
|
|
16929
16909
|
}
|
|
16930
16910
|
const paths = [];
|
|
16931
16911
|
for (const entry of entries.value) {
|
|
16932
|
-
const path =
|
|
16912
|
+
const path = join3(directory, entry.name);
|
|
16933
16913
|
if (entry.isDirectory()) {
|
|
16934
16914
|
const nestedPaths = await listMarkdownFiles(root, path);
|
|
16935
16915
|
if (!nestedPaths.ok) {
|
|
@@ -16950,14 +16930,14 @@ function compareStrings2(left, right) {
|
|
|
16950
16930
|
}
|
|
16951
16931
|
|
|
16952
16932
|
// ../core/src/memory/user.ts
|
|
16953
|
-
import { join as
|
|
16933
|
+
import { join as join4 } from "node:path";
|
|
16954
16934
|
async function loadUserPartition(root, loadFiles) {
|
|
16955
16935
|
if (!loadFiles) {
|
|
16956
16936
|
const directory = await readDirectory2(root);
|
|
16957
16937
|
if (!directory.ok) {
|
|
16958
16938
|
return hasNodeErrorCode(directory.error.cause, "ENOENT") ? success({ exists: false }) : directory;
|
|
16959
16939
|
}
|
|
16960
|
-
for (const path of [
|
|
16940
|
+
for (const path of [join4(root, "operator.md"), join4(root, "catalog.md")]) {
|
|
16961
16941
|
const file = await checkOptionalReadableFile(path);
|
|
16962
16942
|
if (!file.ok) {
|
|
16963
16943
|
return file;
|
|
@@ -16965,11 +16945,11 @@ async function loadUserPartition(root, loadFiles) {
|
|
|
16965
16945
|
}
|
|
16966
16946
|
return success({ exists: true });
|
|
16967
16947
|
}
|
|
16968
|
-
const operatorInstructions = await readOptionalContent(
|
|
16948
|
+
const operatorInstructions = await readOptionalContent(join4(root, "operator.md"));
|
|
16969
16949
|
if (!operatorInstructions.ok) {
|
|
16970
16950
|
return operatorInstructions;
|
|
16971
16951
|
}
|
|
16972
|
-
const catalog = await readOptionalContent(
|
|
16952
|
+
const catalog = await readOptionalContent(join4(root, "catalog.md"));
|
|
16973
16953
|
if (!catalog.ok) {
|
|
16974
16954
|
return catalog;
|
|
16975
16955
|
}
|
|
@@ -17647,103 +17627,6 @@ var describeFile = fn2("describeFile")(function* (cwd, root, file, label) {
|
|
|
17647
17627
|
];
|
|
17648
17628
|
});
|
|
17649
17629
|
|
|
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
17630
|
// src/update.ts
|
|
17748
17631
|
import { realpathSync } from "node:fs";
|
|
17749
17632
|
import { sep as sep2 } from "node:path";
|
|
@@ -17897,6 +17780,103 @@ var upgrade = fn2("upgrade")(function* (context) {
|
|
|
17897
17780
|
}
|
|
17898
17781
|
});
|
|
17899
17782
|
|
|
17783
|
+
// src/commands/user/common.ts
|
|
17784
|
+
var USER_ROOT = "~/.operator/user";
|
|
17785
|
+
var USER_FILES = ["operator.md", "catalog.md"];
|
|
17786
|
+
|
|
17787
|
+
// src/commands/user/init.ts
|
|
17788
|
+
var userInit = fn2("userInit")((context) => gen2(function* () {
|
|
17789
|
+
const pathService = yield* Path;
|
|
17790
|
+
const root = pathService.join(context.home, ".operator", "user");
|
|
17791
|
+
yield* ensurePrivateDirectory(pathService.join(context.home, ".operator"));
|
|
17792
|
+
const templates = {
|
|
17793
|
+
"operator.md": readTemplate("user/operator.md" /* UserOperator */),
|
|
17794
|
+
"catalog.md": readTemplate("user/catalog.md" /* UserCatalog */)
|
|
17795
|
+
};
|
|
17796
|
+
const rows = [];
|
|
17797
|
+
let failed = false;
|
|
17798
|
+
for (const file of USER_FILES) {
|
|
17799
|
+
const written = yield* writeTextIfMissing(pathService.join(root, file), templates[file]).pipe(result2);
|
|
17800
|
+
if (isFailure2(written)) {
|
|
17801
|
+
failed = true;
|
|
17802
|
+
rows.push(["Failed", `${USER_ROOT}/${file}: ${fileErrorMessage(written.failure)}`]);
|
|
17803
|
+
} else {
|
|
17804
|
+
rows.push([
|
|
17805
|
+
written.success === "created" ? "Created" : "Preserved",
|
|
17806
|
+
`${USER_ROOT}/${file}`
|
|
17807
|
+
]);
|
|
17808
|
+
}
|
|
17809
|
+
}
|
|
17810
|
+
return {
|
|
17811
|
+
exitCode: failed ? 1 : 0,
|
|
17812
|
+
output: `✓ User Partition Initialized
|
|
17813
|
+
${renderTable(rows)}`
|
|
17814
|
+
};
|
|
17815
|
+
}).pipe(catch_2((error) => succeed6(fileFailure(error))), map3((result) => ({
|
|
17816
|
+
...result,
|
|
17817
|
+
output: `${result.output}
|
|
17818
|
+
|
|
17819
|
+
Follow the agent instructions below to complete User Setup.
|
|
17820
|
+
|
|
17821
|
+
${readTemplate("user/setup.md" /* UserSetup */)}`
|
|
17822
|
+
}))));
|
|
17823
|
+
|
|
17824
|
+
// src/commands/user/status.ts
|
|
17825
|
+
var INIT_HINT = [
|
|
17826
|
+
"For Users: Run `/operator:user-init` in your harness to initialize.",
|
|
17827
|
+
"For Agents: Run `operator-helper user init` and follow the guide in its output."
|
|
17828
|
+
];
|
|
17829
|
+
var userStatus = fn2("userStatus")(function* (context) {
|
|
17830
|
+
const pathService = yield* Path;
|
|
17831
|
+
const root = pathService.join(context.home, ".operator", "user");
|
|
17832
|
+
const kind = yield* inspectPath(root);
|
|
17833
|
+
if (kind === "missing") {
|
|
17834
|
+
return {
|
|
17835
|
+
exitCode: 0,
|
|
17836
|
+
output: [
|
|
17837
|
+
`User Partition: ${USER_ROOT}`,
|
|
17838
|
+
"",
|
|
17839
|
+
"⚠ User Partition Missing",
|
|
17840
|
+
renderTable([["Expected Location", `${USER_ROOT}/`]]),
|
|
17841
|
+
"",
|
|
17842
|
+
...INIT_HINT
|
|
17843
|
+
].join(`
|
|
17844
|
+
`)
|
|
17845
|
+
};
|
|
17846
|
+
}
|
|
17847
|
+
if (kind !== "directory") {
|
|
17848
|
+
return yield* new PathKindError({
|
|
17849
|
+
path: root,
|
|
17850
|
+
expected: "directory",
|
|
17851
|
+
actual: kind
|
|
17852
|
+
});
|
|
17853
|
+
}
|
|
17854
|
+
const rows = [];
|
|
17855
|
+
let missingCore = false;
|
|
17856
|
+
for (const [file, label] of [
|
|
17857
|
+
["operator.md", "User Instructions"],
|
|
17858
|
+
["catalog.md", "User Catalog"]
|
|
17859
|
+
]) {
|
|
17860
|
+
const content = yield* readOptionalText(pathService.join(root, file));
|
|
17861
|
+
if (content === null) {
|
|
17862
|
+
missingCore = true;
|
|
17863
|
+
}
|
|
17864
|
+
rows.push([
|
|
17865
|
+
label,
|
|
17866
|
+
content === null ? `Missing (${USER_ROOT}/${file})` : `${USER_ROOT}/${file} (${Buffer.byteLength(content)} bytes)`
|
|
17867
|
+
]);
|
|
17868
|
+
}
|
|
17869
|
+
const files = yield* listFiles(root);
|
|
17870
|
+
const freeform = files.filter((file) => file.relativePath !== "operator.md" && file.relativePath !== "catalog.md");
|
|
17871
|
+
rows.push(["Freeform Content", `${freeform.length} files`]);
|
|
17872
|
+
const lines = [`User Partition: ${USER_ROOT}`, "", "✓ User Partition Found", renderTable(rows)];
|
|
17873
|
+
if (missingCore) {
|
|
17874
|
+
lines.push("", ...INIT_HINT);
|
|
17875
|
+
}
|
|
17876
|
+
return { exitCode: 0, output: lines.join(`
|
|
17877
|
+
`) };
|
|
17878
|
+
});
|
|
17879
|
+
|
|
17900
17880
|
// src/commands/version.ts
|
|
17901
17881
|
var version = fn2("version")(function* (context) {
|
|
17902
17882
|
const latest = yield* checkForUpdate(context.version).pipe(catch_2((error) => succeed6({ status: "failed", message: error.message })));
|
|
@@ -17928,7 +17908,7 @@ ${renderTable([
|
|
|
17928
17908
|
["install opencode-v2", "Install or update the OpenCode V2 plugin"],
|
|
17929
17909
|
["install deepseek", "Install or update the DeepSeek Harness plugin"],
|
|
17930
17910
|
["install pi", "Install or update the Pi plugin"],
|
|
17931
|
-
["install code
|
|
17911
|
+
["install claude-code", "Install or update the Claude Code plugin"]
|
|
17932
17912
|
])}
|
|
17933
17913
|
|
|
17934
17914
|
AGENT COMMANDS
|
|
@@ -17979,6 +17959,8 @@ function runCli(arguments_, context) {
|
|
|
17979
17959
|
return yield* memoryCheck(context);
|
|
17980
17960
|
case "install codex":
|
|
17981
17961
|
return yield* installCodex(context);
|
|
17962
|
+
case "install claude-code":
|
|
17963
|
+
return yield* installClaudeCode(context);
|
|
17982
17964
|
case "install opencode":
|
|
17983
17965
|
return yield* installOpenCode(context);
|
|
17984
17966
|
case "install opencode-v2":
|
|
@@ -17987,8 +17969,6 @@ function runCli(arguments_, context) {
|
|
|
17987
17969
|
return yield* installDeepSeek(context);
|
|
17988
17970
|
case "install pi":
|
|
17989
17971
|
return yield* installPi(context);
|
|
17990
|
-
case "install code-puppy":
|
|
17991
|
-
return yield* installCodePuppy(context);
|
|
17992
17972
|
case "user guide":
|
|
17993
17973
|
case "project guide":
|
|
17994
17974
|
case "index guide":
|
|
@@ -18027,4 +18007,4 @@ var main = gen2(function* () {
|
|
|
18027
18007
|
}).pipe(provide4(LiveLayers));
|
|
18028
18008
|
runMain2(main);
|
|
18029
18009
|
|
|
18030
|
-
//# debugId=
|
|
18010
|
+
//# debugId=BD7F91EF8CB96E6A64756E2164756E21
|
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()
|