@code-yeongyu/senpi 2026.9.26 → 2026.9.27-2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +43 -0
- package/dist/bun-runtime.d.ts +18 -2
- package/dist/bun-runtime.d.ts.map +1 -1
- package/dist/bun-runtime.js +60 -8
- package/dist/bun-runtime.js.map +1 -1
- package/dist/bundle/chunks/{app-server-command-ZRCKWY2X.js → app-server-command-ATWHNOOO.js} +1 -1
- package/dist/bundle/chunks/bun-extension-importer-R4HXF3GD.js +14 -0
- package/dist/bundle/chunks/chunk-2L26KPZ6.js +8 -0
- package/dist/bundle/chunks/{chunk-3WBSBDBX.js → chunk-3CC5ZOBV.js} +1 -1
- package/dist/bundle/chunks/{chunk-OZCB6BP3.js → chunk-67BC7VZV.js} +4 -4
- package/dist/bundle/chunks/{chunk-QGC46CGK.js → chunk-6SLQ5HFO.js} +46 -43
- package/dist/bundle/chunks/{chunk-MKAM7IRH.js → chunk-6W2DB2GC.js} +1 -1
- package/dist/bundle/chunks/{chunk-BSOJHK4W.js → chunk-7FXXRPB3.js} +2 -1
- package/dist/bundle/chunks/{chunk-Y54V65MS.js → chunk-HPY47AVX.js} +1 -1
- package/dist/bundle/chunks/{chunk-CLZVNQW3.js → chunk-LPFZGWG2.js} +1 -1
- package/dist/bundle/chunks/chunk-W3D3ZYDS.js +2 -0
- package/dist/bundle/chunks/{chunk-UBPMXBOJ.js → chunk-XKLR7C5B.js} +1 -1
- package/dist/bundle/chunks/{cli-main-H6JAXAXV.js → cli-main-HOM6UH3H.js} +1 -1
- package/dist/bundle/chunks/{help-fast-path-ZGKPSDFO.js → help-fast-path-YGYHMV2W.js} +1 -1
- package/dist/bundle/chunks/{host-command-BMHJ4YUD.js → host-command-XXSAFWBW.js} +1 -1
- package/dist/bundle/chunks/{host-lifecycle-2GZAGSGW.js → host-lifecycle-GOMGJ25O.js} +1 -1
- package/dist/bundle/chunks/{interactive-host-runtime-YI75GFD5.js → interactive-host-runtime-IYCZWJGX.js} +1 -1
- package/dist/bundle/chunks/{interactive-mode-HM7UQYRC.js → interactive-mode-ZMO7YVYU.js} +1 -1
- package/dist/bundle/chunks/{multi-session-host-7YX2I7UK.js → multi-session-host-CE3W6COI.js} +1 -1
- package/dist/bundle/chunks/{package-manager-cli-3ISYTEL2.js → package-manager-cli-2PHRL2SX.js} +1 -1
- package/dist/bundle/chunks/{rpc-mode-6PY4TN5F.js → rpc-mode-WCETTMFV.js} +1 -1
- package/dist/bundle/chunks/session-worker.js +133 -129
- package/dist/bundle/cli.js +1 -1
- package/dist/bundle/index.js +1 -1
- package/dist/bundle/rpc-entry.js +1 -1
- package/dist/cli/args.d.ts +1 -0
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +4 -0
- package/dist/cli/args.js.map +1 -1
- package/dist/cli/cross-project-session.d.ts +22 -0
- package/dist/cli/cross-project-session.d.ts.map +1 -0
- package/dist/cli/cross-project-session.js +38 -0
- package/dist/cli/cross-project-session.js.map +1 -0
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +19 -5
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/extensions/builtin/index.d.ts.map +1 -1
- package/dist/core/extensions/builtin/index.js +2 -0
- package/dist/core/extensions/builtin/index.js.map +1 -1
- package/dist/core/extensions/builtin/permission-system/index.d.ts.map +1 -1
- package/dist/core/extensions/builtin/permission-system/index.js +5 -2
- package/dist/core/extensions/builtin/permission-system/index.js.map +1 -1
- package/dist/core/extensions/builtin/permission-system/parsers.d.ts +5 -0
- package/dist/core/extensions/builtin/permission-system/parsers.d.ts.map +1 -1
- package/dist/core/extensions/builtin/permission-system/parsers.js +13 -0
- package/dist/core/extensions/builtin/permission-system/parsers.js.map +1 -1
- package/dist/core/extensions/builtin/repository-identity.d.ts +6 -0
- package/dist/core/extensions/builtin/repository-identity.d.ts.map +1 -0
- package/dist/core/extensions/builtin/repository-identity.js +33 -0
- package/dist/core/extensions/builtin/repository-identity.js.map +1 -0
- package/dist/core/extensions/bun-extension-importer.d.ts.map +1 -1
- package/dist/core/extensions/bun-extension-importer.js +10 -4
- package/dist/core/extensions/bun-extension-importer.js.map +1 -1
- package/dist/core/extensions/bun-extension-registry.js +6 -5
- package/dist/core/extensions/bun-extension-registry.js.map +1 -1
- package/dist/core/extensions/index.d.ts +1 -1
- package/dist/core/extensions/index.d.ts.map +1 -1
- package/dist/core/extensions/index.js.map +1 -1
- package/dist/core/extensions/kernel-prelude.d.ts +13 -0
- package/dist/core/extensions/kernel-prelude.d.ts.map +1 -0
- package/dist/core/extensions/kernel-prelude.js +38 -0
- package/dist/core/extensions/kernel-prelude.js.map +1 -0
- package/dist/core/extensions/types.d.ts +44 -2
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.js.map +1 -1
- package/dist/core/messages.d.ts.map +1 -1
- package/dist/core/messages.js +4 -2
- package/dist/core/messages.js.map +1 -1
- package/dist/core/model-resolver.js +1 -1
- package/dist/core/model-resolver.js.map +1 -1
- package/dist/core/provider-rejected-images.d.ts +18 -0
- package/dist/core/provider-rejected-images.d.ts.map +1 -0
- package/dist/core/provider-rejected-images.js +107 -0
- package/dist/core/provider-rejected-images.js.map +1 -0
- package/dist/core/repository-identity.d.ts +18 -0
- package/dist/core/repository-identity.d.ts.map +1 -0
- package/dist/core/repository-identity.js +75 -0
- package/dist/core/repository-identity.js.map +1 -0
- package/dist/core/session-rebind.d.ts +17 -0
- package/dist/core/session-rebind.d.ts.map +1 -0
- package/dist/core/session-rebind.js +111 -0
- package/dist/core/session-rebind.js.map +1 -0
- package/dist/core/session-title-generator.d.ts.map +1 -1
- package/dist/core/session-title-generator.js +21 -3
- package/dist/core/session-title-generator.js.map +1 -1
- package/dist/core/session-title-reasoning.d.ts +14 -0
- package/dist/core/session-title-reasoning.d.ts.map +1 -0
- package/dist/core/session-title-reasoning.js +31 -0
- package/dist/core/session-title-reasoning.js.map +1 -0
- package/dist/core/tools/path-utils.d.ts.map +1 -1
- package/dist/core/tools/path-utils.js +13 -0
- package/dist/core/tools/path-utils.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/main.d.ts +6 -0
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +100 -19
- package/dist/main.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +2 -0
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/interactive/runtime-notice-presenter.d.ts +8 -0
- package/dist/modes/interactive/runtime-notice-presenter.d.ts.map +1 -0
- package/dist/modes/interactive/runtime-notice-presenter.js +69 -0
- package/dist/modes/interactive/runtime-notice-presenter.js.map +1 -0
- package/dist/modes/interactive/runtime-notice.d.ts +50 -0
- package/dist/modes/interactive/runtime-notice.d.ts.map +1 -0
- package/dist/modes/interactive/runtime-notice.js +104 -0
- package/dist/modes/interactive/runtime-notice.js.map +1 -0
- package/docs/environment-variables.md +2 -0
- package/docs/extensions.md +27 -1
- package/docs/quickstart.md +7 -1
- package/docs/sessions.md +9 -0
- package/docs/usage.md +1 -0
- package/node_modules/@code-yeongyu/senpi-codemode/CHANGELOG.md +26 -0
- package/node_modules/@code-yeongyu/senpi-codemode/README.md +7 -0
- package/node_modules/@code-yeongyu/senpi-codemode/package.json +4 -4
- package/node_modules/@code-yeongyu/senpi-codemode/src/bridge/protocol.ts +9 -0
- package/node_modules/@code-yeongyu/senpi-codemode/src/bridges/schema-bridge.ts +2 -0
- package/node_modules/@code-yeongyu/senpi-codemode/src/extension/kernel-preludes.ts +30 -0
- package/node_modules/@code-yeongyu/senpi-codemode/src/index.ts +15 -1
- package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/context-manager.ts +1 -1
- package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/local-module-loader.ts +16 -2
- package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/py/kernel.ts +6 -1
- package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/py/prelude.py +15 -2
- package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/py/transport.ts +7 -1
- package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/shared/kernel-prelude-plan.ts +19 -0
- package/node_modules/@code-yeongyu/senpi-codemode/src/prompt/eval-prompt-template.ts +2 -1
- package/node_modules/@code-yeongyu/senpi-codemode/src/prompt/eval-prompt.ts +4 -0
- package/node_modules/@code-yeongyu/senpi-codemode/src/tool/eval-tool-options.ts +11 -1
- package/node_modules/@code-yeongyu/senpi-codemode/src/tool/eval-tool.ts +1 -0
- package/node_modules/@code-yeongyu/senpi-codemode/src/tool/run-eval-cell.ts +1 -0
- package/node_modules/@code-yeongyu/senpi-codemode/src/tool/types.ts +3 -1
- package/node_modules/@earendil-works/pi-agent-core/package.json +3 -3
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/.manifest.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/fireworks.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/opencode-go.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/opencode.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/openrouter.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/vercel-ai-gateway.json +1 -1
- package/node_modules/@earendil-works/pi-ai/package.json +2 -2
- package/node_modules/@earendil-works/pi-pty/native/prebuilds/darwin-arm64/senpi_pty.darwin-arm64.node +0 -0
- package/node_modules/@earendil-works/pi-pty/package.json +1 -1
- package/node_modules/@earendil-works/pi-telemetry/package.json +1 -1
- package/node_modules/@earendil-works/pi-tui/package.json +1 -1
- package/package.json +7 -7
- package/dist/bundle/chunks/bun-extension-importer-H4IJCWWQ.js +0 -14
- package/dist/bundle/chunks/chunk-34HMVB33.js +0 -2
- package/dist/bundle/chunks/chunk-T3JBT2IK.js +0 -8
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type BunRuntimeOptions } from "../../bun-runtime.ts";
|
|
2
|
+
import type { NoticeSpec } from "../../core/extensions/notice/index.ts";
|
|
3
|
+
import { type BunAvailability } from "./runtime-notice.ts";
|
|
4
|
+
export declare const RUNTIME_NOTICE_STATE_FILE = "runtime-notice.json";
|
|
5
|
+
export declare function readShownVersion(agentDir: string): string | undefined;
|
|
6
|
+
export declare function detectBunAvailability(options: BunRuntimeOptions): BunAvailability;
|
|
7
|
+
export declare function maybeShowRuntimeNotice(show: (spec: NoticeSpec) => void): void;
|
|
8
|
+
//# sourceMappingURL=runtime-notice-presenter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runtime-notice-presenter.d.ts","sourceRoot":"","sources":["../../../src/modes/interactive/runtime-notice-presenter.ts"],"names":[],"mappings":"AAEA,OAAO,EACN,KAAK,iBAAiB,EAItB,MAAM,sBAAsB,CAAC;AAG9B,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,uCAAuC,CAAC;AAExE,OAAO,EACN,KAAK,eAAe,EAMpB,MAAM,qBAAqB,CAAC;AAE7B,eAAO,MAAM,yBAAyB,wBAAwB,CAAC;AAE/D,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAUrE;AAYD,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,iBAAiB,GAAG,eAAe,CAOjF;AAOD,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,UAAU,KAAK,IAAI,GAAG,IAAI,CAwB7E"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, realpathSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { bunVersionSatisfies, findBunBinary, processBunRuntimeOptions, } from "../../bun-runtime.js";
|
|
4
|
+
import { APP_COMMAND, APP_NAME, BRAND, detectInstallMethod, getAgentDir, PACKAGE_NAME, VERSION } from "../../config.js";
|
|
5
|
+
import { envValue } from "../../core/brand.js";
|
|
6
|
+
import { hasInheritedInspectorOption } from "../../inspector-policy.js";
|
|
7
|
+
import { buildRuntimeNotice, reinstallTarget, runtimeNoticeSkipReason, runtimePinEnvName, SKIP_RUNTIME_NOTICE_ENV_SUFFIX, } from "./runtime-notice.js";
|
|
8
|
+
export const RUNTIME_NOTICE_STATE_FILE = "runtime-notice.json";
|
|
9
|
+
export function readShownVersion(agentDir) {
|
|
10
|
+
let parsed;
|
|
11
|
+
try {
|
|
12
|
+
parsed = JSON.parse(readFileSync(join(agentDir, RUNTIME_NOTICE_STATE_FILE), "utf8"));
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
// Missing or unreadable state means the notice was never recorded as shown.
|
|
16
|
+
return undefined;
|
|
17
|
+
}
|
|
18
|
+
if (typeof parsed !== "object" || parsed === null || !("shownVersion" in parsed))
|
|
19
|
+
return undefined;
|
|
20
|
+
return typeof parsed.shownVersion === "string" ? parsed.shownVersion : undefined;
|
|
21
|
+
}
|
|
22
|
+
function recordShownVersion(agentDir, version) {
|
|
23
|
+
try {
|
|
24
|
+
mkdirSync(agentDir, { recursive: true });
|
|
25
|
+
writeFileSync(join(agentDir, RUNTIME_NOTICE_STATE_FILE), `${JSON.stringify({ shownVersion: version })}\n`);
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
// Best effort: a read-only agent dir only means the notice shows again next launch,
|
|
29
|
+
// which must never fail the interactive startup that is already underway.
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
export function detectBunAvailability(options) {
|
|
33
|
+
const bunPath = findBunBinary(options);
|
|
34
|
+
if (bunPath === undefined)
|
|
35
|
+
return { kind: "missing" };
|
|
36
|
+
const version = options.bunVersion(bunPath);
|
|
37
|
+
return version !== undefined && bunVersionSatisfies(version)
|
|
38
|
+
? { kind: "ready", version }
|
|
39
|
+
: { kind: "outdated", version };
|
|
40
|
+
}
|
|
41
|
+
function skipRequested() {
|
|
42
|
+
const value = envValue(SKIP_RUNTIME_NOTICE_ENV_SUFFIX);
|
|
43
|
+
return value !== undefined && value !== "" && value !== "0";
|
|
44
|
+
}
|
|
45
|
+
export function maybeShowRuntimeNotice(show) {
|
|
46
|
+
const agentDir = getAgentDir();
|
|
47
|
+
const skip = runtimeNoticeSkipReason({
|
|
48
|
+
versions: process.versions,
|
|
49
|
+
env: process.env,
|
|
50
|
+
hasInheritedInspectorOption: hasInheritedInspectorOption(),
|
|
51
|
+
skipRequested: skipRequested(),
|
|
52
|
+
engineVersion: VERSION,
|
|
53
|
+
shownVersion: readShownVersion(agentDir),
|
|
54
|
+
});
|
|
55
|
+
if (skip !== undefined)
|
|
56
|
+
return;
|
|
57
|
+
show(buildRuntimeNotice({
|
|
58
|
+
appName: APP_NAME,
|
|
59
|
+
appCommand: APP_COMMAND,
|
|
60
|
+
nodeVersion: process.version,
|
|
61
|
+
platform: process.platform,
|
|
62
|
+
installMethod: detectInstallMethod(),
|
|
63
|
+
target: reinstallTarget(BRAND, PACKAGE_NAME),
|
|
64
|
+
bun: detectBunAvailability(processBunRuntimeOptions(existsSync, realpathSync)),
|
|
65
|
+
pinEnvName: runtimePinEnvName(process.env),
|
|
66
|
+
}));
|
|
67
|
+
recordShownVersion(agentDir, VERSION);
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=runtime-notice-presenter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runtime-notice-presenter.js","sourceRoot":"","sources":["../../../src/modes/interactive/runtime-notice-presenter.ts"],"names":[],"mappings":"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","sourcesContent":["import { existsSync, mkdirSync, readFileSync, realpathSync, writeFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport {\n\ttype BunRuntimeOptions,\n\tbunVersionSatisfies,\n\tfindBunBinary,\n\tprocessBunRuntimeOptions,\n} from \"../../bun-runtime.ts\";\nimport { APP_COMMAND, APP_NAME, BRAND, detectInstallMethod, getAgentDir, PACKAGE_NAME, VERSION } from \"../../config.ts\";\nimport { envValue } from \"../../core/brand.ts\";\nimport type { NoticeSpec } from \"../../core/extensions/notice/index.ts\";\nimport { hasInheritedInspectorOption } from \"../../inspector-policy.ts\";\nimport {\n\ttype BunAvailability,\n\tbuildRuntimeNotice,\n\treinstallTarget,\n\truntimeNoticeSkipReason,\n\truntimePinEnvName,\n\tSKIP_RUNTIME_NOTICE_ENV_SUFFIX,\n} from \"./runtime-notice.ts\";\n\nexport const RUNTIME_NOTICE_STATE_FILE = \"runtime-notice.json\";\n\nexport function readShownVersion(agentDir: string): string | undefined {\n\tlet parsed: unknown;\n\ttry {\n\t\tparsed = JSON.parse(readFileSync(join(agentDir, RUNTIME_NOTICE_STATE_FILE), \"utf8\"));\n\t} catch {\n\t\t// Missing or unreadable state means the notice was never recorded as shown.\n\t\treturn undefined;\n\t}\n\tif (typeof parsed !== \"object\" || parsed === null || !(\"shownVersion\" in parsed)) return undefined;\n\treturn typeof parsed.shownVersion === \"string\" ? parsed.shownVersion : undefined;\n}\n\nfunction recordShownVersion(agentDir: string, version: string): void {\n\ttry {\n\t\tmkdirSync(agentDir, { recursive: true });\n\t\twriteFileSync(join(agentDir, RUNTIME_NOTICE_STATE_FILE), `${JSON.stringify({ shownVersion: version })}\\n`);\n\t} catch {\n\t\t// Best effort: a read-only agent dir only means the notice shows again next launch,\n\t\t// which must never fail the interactive startup that is already underway.\n\t}\n}\n\nexport function detectBunAvailability(options: BunRuntimeOptions): BunAvailability {\n\tconst bunPath = findBunBinary(options);\n\tif (bunPath === undefined) return { kind: \"missing\" };\n\tconst version = options.bunVersion(bunPath);\n\treturn version !== undefined && bunVersionSatisfies(version)\n\t\t? { kind: \"ready\", version }\n\t\t: { kind: \"outdated\", version };\n}\n\nfunction skipRequested(): boolean {\n\tconst value = envValue(SKIP_RUNTIME_NOTICE_ENV_SUFFIX);\n\treturn value !== undefined && value !== \"\" && value !== \"0\";\n}\n\nexport function maybeShowRuntimeNotice(show: (spec: NoticeSpec) => void): void {\n\tconst agentDir = getAgentDir();\n\tconst skip = runtimeNoticeSkipReason({\n\t\tversions: process.versions,\n\t\tenv: process.env,\n\t\thasInheritedInspectorOption: hasInheritedInspectorOption(),\n\t\tskipRequested: skipRequested(),\n\t\tengineVersion: VERSION,\n\t\tshownVersion: readShownVersion(agentDir),\n\t});\n\tif (skip !== undefined) return;\n\tshow(\n\t\tbuildRuntimeNotice({\n\t\t\tappName: APP_NAME,\n\t\t\tappCommand: APP_COMMAND,\n\t\t\tnodeVersion: process.version,\n\t\t\tplatform: process.platform,\n\t\t\tinstallMethod: detectInstallMethod(),\n\t\t\ttarget: reinstallTarget(BRAND, PACKAGE_NAME),\n\t\t\tbun: detectBunAvailability(processBunRuntimeOptions(existsSync, realpathSync)),\n\t\t\tpinEnvName: runtimePinEnvName(process.env),\n\t\t}),\n\t);\n\trecordShownVersion(agentDir, VERSION);\n}\n"]}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { InstallMethod } from "../../config.ts";
|
|
2
|
+
import type { BrandProfile } from "../../core/brand.ts";
|
|
3
|
+
import type { NoticeSpec } from "../../core/extensions/notice/index.ts";
|
|
4
|
+
export declare const SKIP_RUNTIME_NOTICE_ENV_SUFFIX = "SKIP_RUNTIME_NOTICE";
|
|
5
|
+
export type RuntimeNoticeSkip = "bun-runtime" | "node-pinned" | "inspector" | "skip-requested" | "already-shown";
|
|
6
|
+
export interface RuntimeNoticeGate {
|
|
7
|
+
readonly versions: Readonly<Record<string, string | undefined>>;
|
|
8
|
+
readonly env: NodeJS.ProcessEnv;
|
|
9
|
+
readonly hasInheritedInspectorOption: boolean;
|
|
10
|
+
readonly skipRequested: boolean;
|
|
11
|
+
readonly engineVersion: string;
|
|
12
|
+
readonly shownVersion: string | undefined;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* The variable that pins the runtime from the user's side. The OmO Native launcher always
|
|
16
|
+
* forwards its own runtime as `SENPI_RUNTIME`, so under OmO Native only `OMO_RUNTIME` is the
|
|
17
|
+
* user's choice; a forwarded `SENPI_RUNTIME=node` just means the launcher could not use Bun.
|
|
18
|
+
*/
|
|
19
|
+
export declare function runtimePinEnvName(env: NodeJS.ProcessEnv): string;
|
|
20
|
+
export declare function runtimeNoticeSkipReason(gate: RuntimeNoticeGate): RuntimeNoticeSkip | undefined;
|
|
21
|
+
export type BunAvailability = {
|
|
22
|
+
readonly kind: "missing";
|
|
23
|
+
} | {
|
|
24
|
+
readonly kind: "outdated";
|
|
25
|
+
readonly version: string | undefined;
|
|
26
|
+
} | {
|
|
27
|
+
readonly kind: "ready";
|
|
28
|
+
readonly version: string;
|
|
29
|
+
};
|
|
30
|
+
export interface ReinstallTarget {
|
|
31
|
+
readonly packageName: string;
|
|
32
|
+
readonly installSpec: string;
|
|
33
|
+
readonly ignoreScripts: boolean;
|
|
34
|
+
}
|
|
35
|
+
export declare function reinstallTarget(brand: BrandProfile | undefined, packageName: string): ReinstallTarget | undefined;
|
|
36
|
+
/** Clean reinstall: remove the Node-managed copy so PATH cannot keep resolving it, then add it with Bun. */
|
|
37
|
+
export declare function bunReinstallCommand(method: InstallMethod, target: ReinstallTarget): string;
|
|
38
|
+
export declare function installBunCommand(platform: NodeJS.Platform): string;
|
|
39
|
+
export interface RuntimeNoticeInput {
|
|
40
|
+
readonly appName: string;
|
|
41
|
+
readonly appCommand: string;
|
|
42
|
+
readonly nodeVersion: string;
|
|
43
|
+
readonly platform: NodeJS.Platform;
|
|
44
|
+
readonly installMethod: InstallMethod;
|
|
45
|
+
readonly target: ReinstallTarget | undefined;
|
|
46
|
+
readonly bun: BunAvailability;
|
|
47
|
+
readonly pinEnvName: string;
|
|
48
|
+
}
|
|
49
|
+
export declare function buildRuntimeNotice(input: RuntimeNoticeInput): NoticeSpec;
|
|
50
|
+
//# sourceMappingURL=runtime-notice.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runtime-notice.d.ts","sourceRoot":"","sources":["../../../src/modes/interactive/runtime-notice.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AACrD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,KAAK,EAAc,UAAU,EAAE,MAAM,uCAAuC,CAAC;AAEpF,eAAO,MAAM,8BAA8B,wBAAwB,CAAC;AAEpE,MAAM,MAAM,iBAAiB,GAAG,aAAa,GAAG,aAAa,GAAG,WAAW,GAAG,gBAAgB,GAAG,eAAe,CAAC;AAEjH,MAAM,WAAW,iBAAiB;IACjC,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC,CAAC;IAChE,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC,UAAU,CAAC;IAChC,QAAQ,CAAC,2BAA2B,EAAE,OAAO,CAAC;IAC9C,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAC;IAChC,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,YAAY,EAAE,MAAM,GAAG,SAAS,CAAC;CAC1C;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,CAAC,UAAU,GAAG,MAAM,CAEhE;AAED,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,iBAAiB,GAAG,iBAAiB,GAAG,SAAS,CAO9F;AAED,MAAM,MAAM,eAAe,GACxB;IAAE,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAA;CAAE,GAC5B;IAAE,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,CAAA;CAAE,GACnE;IAAE,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAExD,MAAM,WAAW,eAAe;IAC/B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAC;CAChC;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,YAAY,GAAG,SAAS,EAAE,WAAW,EAAE,MAAM,GAAG,eAAe,GAAG,SAAS,CAQjH;AAiBD,4GAA4G;AAC5G,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,aAAa,EAAE,MAAM,EAAE,eAAe,GAAG,MAAM,CAI1F;AAED,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,CAAC,QAAQ,GAAG,MAAM,CAInE;AAED,MAAM,WAAW,kBAAkB;IAClC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC,QAAQ,CAAC;IACnC,QAAQ,CAAC,aAAa,EAAE,aAAa,CAAC;IACtC,QAAQ,CAAC,MAAM,EAAE,eAAe,GAAG,SAAS,CAAC;IAC7C,QAAQ,CAAC,GAAG,EAAE,eAAe,CAAC;IAC9B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;CAC5B;AAyBD,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,kBAAkB,GAAG,UAAU,CAaxE"}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The "running on Node.js" startup notice: when to show it and what it says.
|
|
3
|
+
*
|
|
4
|
+
* The only trigger is the runtime itself. Every install path that can run on Bun already
|
|
5
|
+
* re-execs there (`bun-runtime.ts` for senpi, the OmO Native launcher for omo), so a process
|
|
6
|
+
* still on Node here is one the user can fix: Bun is missing or too old, or the package was
|
|
7
|
+
* installed by a manager that keeps it on Node. Everything in this file is pure; the
|
|
8
|
+
* interactive mode supplies the facts through `runtime-notice-presenter.ts`.
|
|
9
|
+
*/
|
|
10
|
+
import { MIN_BUN_VERSION, RUNTIME_ENV_VAR } from "../../bun-runtime.js";
|
|
11
|
+
export const SKIP_RUNTIME_NOTICE_ENV_SUFFIX = "SKIP_RUNTIME_NOTICE";
|
|
12
|
+
/**
|
|
13
|
+
* The variable that pins the runtime from the user's side. The OmO Native launcher always
|
|
14
|
+
* forwards its own runtime as `SENPI_RUNTIME`, so under OmO Native only `OMO_RUNTIME` is the
|
|
15
|
+
* user's choice; a forwarded `SENPI_RUNTIME=node` just means the launcher could not use Bun.
|
|
16
|
+
*/
|
|
17
|
+
export function runtimePinEnvName(env) {
|
|
18
|
+
return env.OMO_NATIVE === "1" ? "OMO_RUNTIME" : RUNTIME_ENV_VAR;
|
|
19
|
+
}
|
|
20
|
+
export function runtimeNoticeSkipReason(gate) {
|
|
21
|
+
if (gate.versions.bun !== undefined)
|
|
22
|
+
return "bun-runtime";
|
|
23
|
+
if (gate.env[runtimePinEnvName(gate.env)] === "node")
|
|
24
|
+
return "node-pinned";
|
|
25
|
+
if (gate.hasInheritedInspectorOption)
|
|
26
|
+
return "inspector";
|
|
27
|
+
if (gate.skipRequested)
|
|
28
|
+
return "skip-requested";
|
|
29
|
+
if (gate.shownVersion === gate.engineVersion)
|
|
30
|
+
return "already-shown";
|
|
31
|
+
return undefined;
|
|
32
|
+
}
|
|
33
|
+
export function reinstallTarget(brand, packageName) {
|
|
34
|
+
if (brand === undefined)
|
|
35
|
+
return { packageName, installSpec: packageName, ignoreScripts: true };
|
|
36
|
+
const update = brand.update;
|
|
37
|
+
// A brand without an update channel manages its own installs; there is no command to offer.
|
|
38
|
+
if (update === undefined)
|
|
39
|
+
return undefined;
|
|
40
|
+
const installSpec = update.distTag === "latest" ? update.packageName : `${update.packageName}@${update.distTag}`;
|
|
41
|
+
// Branded products may rely on their install script; senpi documents --ignore-scripts.
|
|
42
|
+
return { packageName: update.packageName, installSpec, ignoreScripts: false };
|
|
43
|
+
}
|
|
44
|
+
function uninstallCommand(method, packageName) {
|
|
45
|
+
switch (method) {
|
|
46
|
+
case "npm":
|
|
47
|
+
return `npm uninstall -g ${packageName}`;
|
|
48
|
+
case "pnpm":
|
|
49
|
+
return `pnpm remove -g ${packageName}`;
|
|
50
|
+
case "yarn":
|
|
51
|
+
return `yarn global remove ${packageName}`;
|
|
52
|
+
case "bun":
|
|
53
|
+
case "bun-binary":
|
|
54
|
+
case "unknown":
|
|
55
|
+
return undefined;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/** Clean reinstall: remove the Node-managed copy so PATH cannot keep resolving it, then add it with Bun. */
|
|
59
|
+
export function bunReinstallCommand(method, target) {
|
|
60
|
+
const install = `bun add -g ${target.ignoreScripts ? "--ignore-scripts " : ""}${target.installSpec}`;
|
|
61
|
+
const uninstall = uninstallCommand(method, target.packageName);
|
|
62
|
+
return uninstall === undefined ? install : `${uninstall} && ${install}`;
|
|
63
|
+
}
|
|
64
|
+
export function installBunCommand(platform) {
|
|
65
|
+
return platform === "win32"
|
|
66
|
+
? `powershell -c "irm bun.sh/install.ps1 | iex"`
|
|
67
|
+
: "curl -fsSL https://bun.sh/install | bash";
|
|
68
|
+
}
|
|
69
|
+
function bunStep(input) {
|
|
70
|
+
switch (input.bun.kind) {
|
|
71
|
+
case "missing":
|
|
72
|
+
return { text: `Install Bun: ${installBunCommand(input.platform)}`, tone: "accent" };
|
|
73
|
+
case "outdated":
|
|
74
|
+
return {
|
|
75
|
+
text: `Upgrade Bun (found ${input.bun.version ?? "an unreadable version"}, needs ${MIN_BUN_VERSION}+): bun upgrade`,
|
|
76
|
+
tone: "accent",
|
|
77
|
+
};
|
|
78
|
+
case "ready":
|
|
79
|
+
return undefined;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
function reinstallStep(input, afterBunStep) {
|
|
83
|
+
const lead = afterBunStep ? "Then reinstall" : "Reinstall";
|
|
84
|
+
// A Bun-global install needs no reinstall: a current Bun takes it over on the next launch.
|
|
85
|
+
if (input.installMethod === "bun" || input.target === undefined) {
|
|
86
|
+
return { text: `${afterBunStep ? "Then restart" : "Restart"} ${input.appCommand}.`, tone: "accent" };
|
|
87
|
+
}
|
|
88
|
+
return { text: `${lead} with Bun: ${bunReinstallCommand(input.installMethod, input.target)}`, tone: "accent" };
|
|
89
|
+
}
|
|
90
|
+
export function buildRuntimeNotice(input) {
|
|
91
|
+
const first = bunStep(input);
|
|
92
|
+
const extra = [
|
|
93
|
+
...(first === undefined ? [] : [first]),
|
|
94
|
+
reinstallStep(input, first !== undefined),
|
|
95
|
+
{ text: `To stay on Node.js and hide this notice, set ${input.pinEnvName}=node.` },
|
|
96
|
+
];
|
|
97
|
+
return {
|
|
98
|
+
title: "Running on Node.js",
|
|
99
|
+
tone: "warning",
|
|
100
|
+
why: `${input.appName} is running on Node.js ${input.nodeVersion}. It starts faster and every feature works on Bun ${MIN_BUN_VERSION}+.`,
|
|
101
|
+
extra,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
//# sourceMappingURL=runtime-notice.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runtime-notice.js","sourceRoot":"","sources":["../../../src/modes/interactive/runtime-notice.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The \"running on Node.js\" startup notice: when to show it and what it says.\n *\n * The only trigger is the runtime itself. Every install path that can run on Bun already\n * re-execs there (`bun-runtime.ts` for senpi, the OmO Native launcher for omo), so a process\n * still on Node here is one the user can fix: Bun is missing or too old, or the package was\n * installed by a manager that keeps it on Node. Everything in this file is pure; the\n * interactive mode supplies the facts through `runtime-notice-presenter.ts`.\n */\nimport { MIN_BUN_VERSION, RUNTIME_ENV_VAR } from \"../../bun-runtime.ts\";\nimport type { InstallMethod } from \"../../config.ts\";\nimport type { BrandProfile } from \"../../core/brand.ts\";\nimport type { NoticeLine, NoticeSpec } from \"../../core/extensions/notice/index.ts\";\n\nexport const SKIP_RUNTIME_NOTICE_ENV_SUFFIX = \"SKIP_RUNTIME_NOTICE\";\n\nexport type RuntimeNoticeSkip = \"bun-runtime\" | \"node-pinned\" | \"inspector\" | \"skip-requested\" | \"already-shown\";\n\nexport interface RuntimeNoticeGate {\n\treadonly versions: Readonly<Record<string, string | undefined>>;\n\treadonly env: NodeJS.ProcessEnv;\n\treadonly hasInheritedInspectorOption: boolean;\n\treadonly skipRequested: boolean;\n\treadonly engineVersion: string;\n\treadonly shownVersion: string | undefined;\n}\n\n/**\n * The variable that pins the runtime from the user's side. The OmO Native launcher always\n * forwards its own runtime as `SENPI_RUNTIME`, so under OmO Native only `OMO_RUNTIME` is the\n * user's choice; a forwarded `SENPI_RUNTIME=node` just means the launcher could not use Bun.\n */\nexport function runtimePinEnvName(env: NodeJS.ProcessEnv): string {\n\treturn env.OMO_NATIVE === \"1\" ? \"OMO_RUNTIME\" : RUNTIME_ENV_VAR;\n}\n\nexport function runtimeNoticeSkipReason(gate: RuntimeNoticeGate): RuntimeNoticeSkip | undefined {\n\tif (gate.versions.bun !== undefined) return \"bun-runtime\";\n\tif (gate.env[runtimePinEnvName(gate.env)] === \"node\") return \"node-pinned\";\n\tif (gate.hasInheritedInspectorOption) return \"inspector\";\n\tif (gate.skipRequested) return \"skip-requested\";\n\tif (gate.shownVersion === gate.engineVersion) return \"already-shown\";\n\treturn undefined;\n}\n\nexport type BunAvailability =\n\t| { readonly kind: \"missing\" }\n\t| { readonly kind: \"outdated\"; readonly version: string | undefined }\n\t| { readonly kind: \"ready\"; readonly version: string };\n\nexport interface ReinstallTarget {\n\treadonly packageName: string;\n\treadonly installSpec: string;\n\treadonly ignoreScripts: boolean;\n}\n\nexport function reinstallTarget(brand: BrandProfile | undefined, packageName: string): ReinstallTarget | undefined {\n\tif (brand === undefined) return { packageName, installSpec: packageName, ignoreScripts: true };\n\tconst update = brand.update;\n\t// A brand without an update channel manages its own installs; there is no command to offer.\n\tif (update === undefined) return undefined;\n\tconst installSpec = update.distTag === \"latest\" ? update.packageName : `${update.packageName}@${update.distTag}`;\n\t// Branded products may rely on their install script; senpi documents --ignore-scripts.\n\treturn { packageName: update.packageName, installSpec, ignoreScripts: false };\n}\n\nfunction uninstallCommand(method: InstallMethod, packageName: string): string | undefined {\n\tswitch (method) {\n\t\tcase \"npm\":\n\t\t\treturn `npm uninstall -g ${packageName}`;\n\t\tcase \"pnpm\":\n\t\t\treturn `pnpm remove -g ${packageName}`;\n\t\tcase \"yarn\":\n\t\t\treturn `yarn global remove ${packageName}`;\n\t\tcase \"bun\":\n\t\tcase \"bun-binary\":\n\t\tcase \"unknown\":\n\t\t\treturn undefined;\n\t}\n}\n\n/** Clean reinstall: remove the Node-managed copy so PATH cannot keep resolving it, then add it with Bun. */\nexport function bunReinstallCommand(method: InstallMethod, target: ReinstallTarget): string {\n\tconst install = `bun add -g ${target.ignoreScripts ? \"--ignore-scripts \" : \"\"}${target.installSpec}`;\n\tconst uninstall = uninstallCommand(method, target.packageName);\n\treturn uninstall === undefined ? install : `${uninstall} && ${install}`;\n}\n\nexport function installBunCommand(platform: NodeJS.Platform): string {\n\treturn platform === \"win32\"\n\t\t? `powershell -c \"irm bun.sh/install.ps1 | iex\"`\n\t\t: \"curl -fsSL https://bun.sh/install | bash\";\n}\n\nexport interface RuntimeNoticeInput {\n\treadonly appName: string;\n\treadonly appCommand: string;\n\treadonly nodeVersion: string;\n\treadonly platform: NodeJS.Platform;\n\treadonly installMethod: InstallMethod;\n\treadonly target: ReinstallTarget | undefined;\n\treadonly bun: BunAvailability;\n\treadonly pinEnvName: string;\n}\n\nfunction bunStep(input: RuntimeNoticeInput): NoticeLine | undefined {\n\tswitch (input.bun.kind) {\n\t\tcase \"missing\":\n\t\t\treturn { text: `Install Bun: ${installBunCommand(input.platform)}`, tone: \"accent\" };\n\t\tcase \"outdated\":\n\t\t\treturn {\n\t\t\t\ttext: `Upgrade Bun (found ${input.bun.version ?? \"an unreadable version\"}, needs ${MIN_BUN_VERSION}+): bun upgrade`,\n\t\t\t\ttone: \"accent\",\n\t\t\t};\n\t\tcase \"ready\":\n\t\t\treturn undefined;\n\t}\n}\n\nfunction reinstallStep(input: RuntimeNoticeInput, afterBunStep: boolean): NoticeLine {\n\tconst lead = afterBunStep ? \"Then reinstall\" : \"Reinstall\";\n\t// A Bun-global install needs no reinstall: a current Bun takes it over on the next launch.\n\tif (input.installMethod === \"bun\" || input.target === undefined) {\n\t\treturn { text: `${afterBunStep ? \"Then restart\" : \"Restart\"} ${input.appCommand}.`, tone: \"accent\" };\n\t}\n\treturn { text: `${lead} with Bun: ${bunReinstallCommand(input.installMethod, input.target)}`, tone: \"accent\" };\n}\n\nexport function buildRuntimeNotice(input: RuntimeNoticeInput): NoticeSpec {\n\tconst first = bunStep(input);\n\tconst extra: NoticeLine[] = [\n\t\t...(first === undefined ? [] : [first]),\n\t\treinstallStep(input, first !== undefined),\n\t\t{ text: `To stay on Node.js and hide this notice, set ${input.pinEnvName}=node.` },\n\t];\n\treturn {\n\t\ttitle: \"Running on Node.js\",\n\t\ttone: \"warning\",\n\t\twhy: `${input.appName} is running on Node.js ${input.nodeVersion}. It starts faster and every feature works on Bun ${MIN_BUN_VERSION}+.`,\n\t\textra,\n\t};\n}\n"]}
|
|
@@ -87,6 +87,8 @@ These variables are read by Pi itself:
|
|
|
87
87
|
| `PI_PACKAGE_DIR` | Override the package directory, useful for Nix/Guix store paths |
|
|
88
88
|
| `PI_OFFLINE` | Disable startup network operations, including update checks, package updates, and install/update telemetry |
|
|
89
89
|
| `PI_SKIP_VERSION_CHECK` | Disable the `pi.dev` latest-version request |
|
|
90
|
+
| `SENPI_RUNTIME` | `node` keeps senpi on Node.js and hides the Node.js runtime notice; `bun` re-execs under any installed Bun. Without it, a Bun-global install runs on its Bun and any other installed package runs on a discovered Bun 1.4.0 or newer |
|
|
91
|
+
| `PI_SKIP_RUNTIME_NOTICE` | Hide the one-time "Running on Node.js" notice without changing the runtime |
|
|
90
92
|
| `PI_TELEMETRY` | Override install/update telemetry and provider attribution headers: `1`/`true`/`yes` or `0`/`false`/`no` |
|
|
91
93
|
| `PI_CACHE_RETENTION` | Set to `long` to opt into extended provider prompt caching where supported; direct Anthropic defaults to 5 minutes |
|
|
92
94
|
| `PI_SHARE_VIEWER_URL` | Override the base URL used by `/share` |
|
package/docs/extensions.md
CHANGED
|
@@ -936,6 +936,17 @@ pi.on("thinking_level_select", async (event, ctx) => {
|
|
|
936
936
|
|
|
937
937
|
Use this to update extension UI when `pi.setThinkingLevel()`, model changes, or built-in thinking-level controls change the active thinking level.
|
|
938
938
|
|
|
939
|
+
#### tool_activated
|
|
940
|
+
|
|
941
|
+
Fired after the active tool set gains tools. This covers `pi.setActiveTools()`, a `tool_search` promotion, and a by-name call that activates a deferred tool. The event is notification-only: handler return values are ignored.
|
|
942
|
+
|
|
943
|
+
```typescript
|
|
944
|
+
pi.on("tool_activated", async (event, ctx) => {
|
|
945
|
+
// event.toolNames - only the tools that just became active
|
|
946
|
+
if (event.toolNames.includes("my_tool")) await warmUp(ctx);
|
|
947
|
+
});
|
|
948
|
+
```
|
|
949
|
+
|
|
939
950
|
### Tool Events
|
|
940
951
|
|
|
941
952
|
#### tool_call
|
|
@@ -1676,6 +1687,21 @@ Use `promptSnippet` to opt a custom tool into a one-line entry in `Available too
|
|
|
1676
1687
|
|
|
1677
1688
|
**Important:** `promptGuidelines` bullets are appended flat to the `Guidelines` section with no tool name prefix. Each guideline must name the tool it refers to — avoid "Use this tool when..." because the LLM cannot tell which tool "this" means. Write "Use my_tool when..." instead.
|
|
1678
1689
|
|
|
1690
|
+
Use `kernelPrelude` to give the `eval` kernels globals that call your tool while it is active. It takes `javascript` and `python` statements, one `documentation` line for the eval prompt's helper list, and the `exports` those statements define. Each snippet runs before a cell only when one of its exports is missing, and a deactivated tool's exports are removed before the next cell. Exports must not shadow built-in kernel helpers such as `display` or `tool`.
|
|
1691
|
+
|
|
1692
|
+
Use `permissionParser` to classify your tool's calls for the permission system. It receives the call input and the working directory and returns requests (`permission`, `patterns`, `always`), so a rule like `my_tool:read=allow` can grant one tier while another stays gated. Without it, every call is one request named after the tool. A built-in parser for the same tool name always wins.
|
|
1693
|
+
|
|
1694
|
+
```typescript
|
|
1695
|
+
pi.registerTool({
|
|
1696
|
+
name: "my_tool",
|
|
1697
|
+
// ...
|
|
1698
|
+
permissionParser: (input) => {
|
|
1699
|
+
const tier = input.action === "add" ? "exec" : "read";
|
|
1700
|
+
return [{ permission: "my_tool", patterns: [tier], always: [tier] }];
|
|
1701
|
+
},
|
|
1702
|
+
});
|
|
1703
|
+
```
|
|
1704
|
+
|
|
1679
1705
|
See [dynamic-tools.ts](../examples/extensions/dynamic-tools.ts) for a full example.
|
|
1680
1706
|
|
|
1681
1707
|
```typescript
|
|
@@ -2126,7 +2152,7 @@ pi.setActiveTools([...new Set([...active, "my_custom_tool"])]); // Keep current
|
|
|
2126
2152
|
pi.setActiveTools(["read", "bash"]); // Switch to read-only
|
|
2127
2153
|
```
|
|
2128
2154
|
|
|
2129
|
-
`pi.getAllTools()` returns `name`, `description`, `parameters`, `promptGuidelines`, and `sourceInfo`.
|
|
2155
|
+
`pi.getAllTools()` returns `name`, `description`, `parameters`, `promptGuidelines`, `kernelPrelude`, `permissionParser`, and `sourceInfo`.
|
|
2130
2156
|
|
|
2131
2157
|
Typical `sourceInfo.source` values:
|
|
2132
2158
|
- `builtin` for built-in tools
|
package/docs/quickstart.md
CHANGED
|
@@ -13,12 +13,18 @@ Senpi is distributed as an npm package:
|
|
|
13
13
|
bun add -g @code-yeongyu/senpi
|
|
14
14
|
```
|
|
15
15
|
|
|
16
|
+
Senpi runs on Bun whenever it can: a `bun add -g` install always does, and an npm, pnpm, or Yarn
|
|
17
|
+
install hands itself to Bun 1.4.0 or newer when one is installed. If senpi still starts on Node.js
|
|
18
|
+
(no Bun, or an older one), the interactive UI shows a "Running on Node.js" notice once per version
|
|
19
|
+
with the commands to install or upgrade Bun and reinstall senpi with it. Set `SENPI_RUNTIME=node`
|
|
20
|
+
to stay on Node.js without the notice.
|
|
21
|
+
|
|
16
22
|
### Uninstall
|
|
17
23
|
|
|
18
24
|
Use the package manager that installed senpi:
|
|
19
25
|
|
|
20
26
|
```bash
|
|
21
|
-
#
|
|
27
|
+
# npm
|
|
22
28
|
npm uninstall -g @code-yeongyu/senpi
|
|
23
29
|
|
|
24
30
|
# pnpm
|
package/docs/sessions.md
CHANGED
|
@@ -13,10 +13,19 @@ senpi --no-session # Ephemeral mode; do not save
|
|
|
13
13
|
senpi --name "my task" # Set session display name at startup
|
|
14
14
|
senpi --session <path|id> # Use a specific session file or partial session ID
|
|
15
15
|
senpi --fork <path|id> # Fork a session file or partial session ID into a new session
|
|
16
|
+
senpi --rebind <path|id> # Move a session of this repository, recorded at another path, here
|
|
16
17
|
```
|
|
17
18
|
|
|
18
19
|
Use `/session` in interactive mode to see the current session file, session ID, message count, tokens, and cost.
|
|
19
20
|
|
|
21
|
+
### Moved or re-cloned repositories
|
|
22
|
+
|
|
23
|
+
Sessions are filed by the directory they started in. Each session also records which git repository that directory belonged to (its root commit and `origin` remote), so it can still be recognised after the repository moves.
|
|
24
|
+
|
|
25
|
+
When `--session <id>` (or a pick in `--resume`) finds a session filed under another path and the current directory is the same git repository, senpi shows both paths and offers to move the session here. Answering `y` rebinds it: the session file moves to the current project, its recorded working directory is updated, and it keeps its id, history, goal, loops, and monitors. The old path no longer lists it. Use `--fork <id>` to copy it into a new session instead. A session from a different repository gets the fork prompt as before.
|
|
26
|
+
|
|
27
|
+
`--rebind <path|id>` does the same without asking, for scripts. It refuses when the two directories are provably different repositories. Without an interactive terminal, `--session` never prompts: it prints the exact `--rebind` and `--fork` commands and exits with a non-zero status.
|
|
28
|
+
|
|
20
29
|
For the JSONL file format and SessionManager API, see [Session Format](session-format.md).
|
|
21
30
|
|
|
22
31
|
## Session Commands
|
package/docs/usage.md
CHANGED
|
@@ -226,6 +226,7 @@ cat README.md | senpi -p "Summarize this text"
|
|
|
226
226
|
| `-r`, `--resume` | Browse and select a session |
|
|
227
227
|
| `--session <path\|id>` | Use a specific session file or partial UUID |
|
|
228
228
|
| `--fork <path\|id>` | Fork a session file or partial UUID into a new session |
|
|
229
|
+
| `--rebind <path\|id>` | Move a session of this repository, recorded at another path (moved or re-cloned), into this directory and continue it |
|
|
229
230
|
| `--session-dir <dir>` | Custom session storage directory |
|
|
230
231
|
| `--no-session` | Ephemeral mode; do not save |
|
|
231
232
|
| `--name <name>`, `-n <name>` | Set session display name at startup |
|
|
@@ -12,6 +12,32 @@
|
|
|
12
12
|
|
|
13
13
|
### Removed
|
|
14
14
|
|
|
15
|
+
## [2026.9.27-2] - 2026-09-27
|
|
16
|
+
|
|
17
|
+
### Breaking Changes
|
|
18
|
+
|
|
19
|
+
### Added
|
|
20
|
+
|
|
21
|
+
- The eval kernels install each active tool's `kernelPrelude`: its JavaScript or Python statements run before a cell whenever one of the prelude's exports is missing, a deactivated tool's exports are removed before the next cell, and each prelude's documentation line joins the eval prompt's helper list. Exports that would shadow a built-in helper are rejected. ([#2178](https://github.com/code-yeongyu/senpi/pull/2178))
|
|
22
|
+
|
|
23
|
+
### Changed
|
|
24
|
+
|
|
25
|
+
### Fixed
|
|
26
|
+
|
|
27
|
+
### Removed
|
|
28
|
+
|
|
29
|
+
## [2026.9.27] - 2026-09-27
|
|
30
|
+
|
|
31
|
+
### Breaking Changes
|
|
32
|
+
|
|
33
|
+
### Added
|
|
34
|
+
|
|
35
|
+
### Changed
|
|
36
|
+
|
|
37
|
+
### Fixed
|
|
38
|
+
|
|
39
|
+
### Removed
|
|
40
|
+
|
|
15
41
|
## [2026.9.26] - 2026-09-26
|
|
16
42
|
|
|
17
43
|
### Breaking Changes
|
|
@@ -162,6 +162,13 @@ into the cell carries the tool's expected parameters, so the cell can correct th
|
|
|
162
162
|
arguments and retry instead of falling back to one-at-a-time tool calls.
|
|
163
163
|
`tool_schema()` exposes the same catalog up front.
|
|
164
164
|
|
|
165
|
+
A tool may also contribute globals of its own through `ToolDefinition.kernelPrelude`
|
|
166
|
+
(JavaScript and Python snippets that call the ordinary `tool.<name>()`, one
|
|
167
|
+
documentation line, and the exported names). While the tool is active, each
|
|
168
|
+
JavaScript and Python cell installs any missing export first and the eval prompt
|
|
169
|
+
lists the documentation line; once the tool is deactivated, the next cell deletes
|
|
170
|
+
those names. Exports that shadow a built-in helper are rejected by the host.
|
|
171
|
+
|
|
165
172
|
`agent()` is available only when the configured task tool is active in the
|
|
166
173
|
session. `output()` similarly requires the configured task-output tool and
|
|
167
174
|
returns immediately: a running task reports its current status, while completed
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@code-yeongyu/senpi-codemode",
|
|
3
|
-
"version": "2026.9.
|
|
3
|
+
"version": "2026.9.27-2",
|
|
4
4
|
"private": true,
|
|
5
5
|
"description": "Source-only senpi extension package for codemode evaluation tools",
|
|
6
6
|
"type": "module",
|
|
@@ -31,14 +31,14 @@
|
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
33
|
"@babel/parser": "8.0.4",
|
|
34
|
-
"@earendil-works/pi-ai": "npm:@code-yeongyu/senpi-ai@2026.9.
|
|
34
|
+
"@earendil-works/pi-ai": "npm:@code-yeongyu/senpi-ai@2026.9.27-2",
|
|
35
35
|
"typebox": "1.3.34"
|
|
36
36
|
},
|
|
37
37
|
"peerDependencies": {
|
|
38
|
-
"@code-yeongyu/senpi": "2026.9.
|
|
38
|
+
"@code-yeongyu/senpi": "2026.9.27-2"
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
41
|
-
"@code-yeongyu/senpi": "2026.9.
|
|
41
|
+
"@code-yeongyu/senpi": "2026.9.27-2"
|
|
42
42
|
},
|
|
43
43
|
"keywords": [
|
|
44
44
|
"senpi",
|
|
@@ -37,6 +37,15 @@ const hostToKernelMessageSchema = Type.Union([
|
|
|
37
37
|
cellId: Type.String({ minLength: 1 }),
|
|
38
38
|
code: Type.String(),
|
|
39
39
|
timeoutMs: Type.Optional(Type.Integer({ minimum: 1 })),
|
|
40
|
+
/** Python only: active tool globals to install before the cell and deactivated ones to remove. */
|
|
41
|
+
preludes: Type.Optional(
|
|
42
|
+
Type.Object({
|
|
43
|
+
install: Type.Array(
|
|
44
|
+
Type.Object({ exports: Type.Array(Type.String({ minLength: 1 })), python: Type.String() }),
|
|
45
|
+
),
|
|
46
|
+
remove: Type.Array(Type.String({ minLength: 1 })),
|
|
47
|
+
}),
|
|
48
|
+
),
|
|
40
49
|
}),
|
|
41
50
|
Type.Object({
|
|
42
51
|
type: Type.Literal("tool-reply"),
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { KernelPreludeContribution } from "@code-yeongyu/senpi";
|
|
1
2
|
import { type Static, Type } from "typebox";
|
|
2
3
|
import { Check, Errors } from "typebox/value";
|
|
3
4
|
|
|
@@ -12,6 +13,7 @@ export interface EvalSchemaToolInfo {
|
|
|
12
13
|
readonly name: string;
|
|
13
14
|
readonly description?: string | undefined;
|
|
14
15
|
readonly parameters?: unknown;
|
|
16
|
+
readonly kernelPrelude?: KernelPreludeContribution | undefined;
|
|
15
17
|
}
|
|
16
18
|
|
|
17
19
|
export interface RunEvalSchemaOptions {
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { KernelPreludeContribution } from "@code-yeongyu/senpi";
|
|
2
|
+
import type { CodemodeRuntimeAPI } from "./runtime-factory.ts";
|
|
3
|
+
|
|
4
|
+
type ToolRegistry = Pick<CodemodeRuntimeAPI, "getActiveTools" | "getAllTools">;
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Kernel globals of the tools active right now. Read per cell, so a tool activated by `tool_search`, a by-name
|
|
8
|
+
* call, or a command shows up in the next cell and a deactivated one disappears, whatever the registration order.
|
|
9
|
+
*/
|
|
10
|
+
export function activeKernelPreludes(pi: ToolRegistry): readonly KernelPreludeContribution[] {
|
|
11
|
+
const active = new Set(pi.getActiveTools());
|
|
12
|
+
return pi.getAllTools().flatMap((tool) => (tool.kernelPrelude && active.has(tool.name) ? [tool.kernelPrelude] : []));
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** Identity of the prompt's contribution doc lines; the eval tool re-registers only when it changes. */
|
|
16
|
+
export function kernelPreludeDocsKey(preludes: readonly KernelPreludeContribution[]): string {
|
|
17
|
+
return preludes.map((prelude) => prelude.documentation).join("\n");
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* The contributions the eval description documents. Like the monitor probe, an unreadable registry (the loader's
|
|
22
|
+
* action methods throw until the runtime binds) teaches nothing; the per-cell read still surfaces any error.
|
|
23
|
+
*/
|
|
24
|
+
export function promptKernelPreludes(pi: ToolRegistry): readonly KernelPreludeContribution[] {
|
|
25
|
+
try {
|
|
26
|
+
return activeKernelPreludes(pi);
|
|
27
|
+
} catch {
|
|
28
|
+
return [];
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
import { EvalNotifier } from "./extension/eval-notifier.ts";
|
|
14
14
|
import { EVAL_CELLS_STATUS_KEY } from "./extension/eval-status.ts";
|
|
15
15
|
import { EvalStatusTicker } from "./extension/eval-status-ticker.ts";
|
|
16
|
+
import { activeKernelPreludes, kernelPreludeDocsKey, promptKernelPreludes } from "./extension/kernel-preludes.ts";
|
|
16
17
|
import {
|
|
17
18
|
createExecuteTool,
|
|
18
19
|
createRuntime,
|
|
@@ -43,7 +44,7 @@ const SESSION_LIFECYCLE_EVENTS = [
|
|
|
43
44
|
|
|
44
45
|
type SessionLifecycleEvent = (typeof SESSION_LIFECYCLE_EVENTS)[number];
|
|
45
46
|
|
|
46
|
-
type CodemodeEvent = SessionLifecycleEvent | "model_select";
|
|
47
|
+
type CodemodeEvent = SessionLifecycleEvent | "model_select" | "turn_start";
|
|
47
48
|
|
|
48
49
|
export interface CodemodeExtensionAPI {
|
|
49
50
|
registerTool(tool: ReturnType<typeof createEvalTool>): void;
|
|
@@ -91,6 +92,7 @@ export default function senpiCodemode(pi: CodemodeExtensionAPI, options: SenpiCo
|
|
|
91
92
|
let activeModelId: string | undefined;
|
|
92
93
|
let activeContext: ExtensionContext | undefined;
|
|
93
94
|
let activeCells: EvalDetachedCellManager | undefined;
|
|
95
|
+
let promptPreludeDocs = "";
|
|
94
96
|
const notifier = new EvalNotifier({
|
|
95
97
|
sendMessage: (message, notifyOptions) => pi.sendMessage(message, notifyOptions),
|
|
96
98
|
getContext: () => activeContext,
|
|
@@ -134,6 +136,8 @@ export default function senpiCodemode(pi: CodemodeExtensionAPI, options: SenpiCo
|
|
|
134
136
|
// reaches this before the runtime is bound). An unreadable registry means "do not teach
|
|
135
137
|
// a tool we cannot confirm"; session_start / model_select re-register once it is live.
|
|
136
138
|
const monitor = monitorIsRegistered(pi);
|
|
139
|
+
const preludes = promptKernelPreludes(pi);
|
|
140
|
+
promptPreludeDocs = kernelPreludeDocsKey(preludes);
|
|
137
141
|
pi.registerTool(
|
|
138
142
|
createEvalTool({
|
|
139
143
|
enabledLanguages: runtime.enabledLanguages,
|
|
@@ -156,6 +160,8 @@ export default function senpiCodemode(pi: CodemodeExtensionAPI, options: SenpiCo
|
|
|
156
160
|
spawnDefaultAgent: runtime.settings.taskTools.task,
|
|
157
161
|
hostLine: hostLine(),
|
|
158
162
|
runtimes: runtime.runtimes,
|
|
163
|
+
kernelPreludes: () => activeKernelPreludes(pi),
|
|
164
|
+
promptKernelPreludes: preludes,
|
|
159
165
|
...(bunSkillPath === undefined ? {} : { bunSkillPath }),
|
|
160
166
|
...(modelId === undefined ? {} : { modelId }),
|
|
161
167
|
}),
|
|
@@ -252,6 +258,14 @@ export default function senpiCodemode(pi: CodemodeExtensionAPI, options: SenpiCo
|
|
|
252
258
|
if (cellManager === undefined) return;
|
|
253
259
|
registerEvalForRuntime(runtime, modelId, cellManager);
|
|
254
260
|
});
|
|
261
|
+
// Tool activation has no event of its own; the next turn re-documents a changed contribution set.
|
|
262
|
+
pi.on("turn_start", async () => {
|
|
263
|
+
const runtime = activeRuntime;
|
|
264
|
+
const cellManager = activeCells;
|
|
265
|
+
if (runtime === undefined || cellManager === undefined) return;
|
|
266
|
+
if (kernelPreludeDocsKey(promptKernelPreludes(pi)) === promptPreludeDocs) return;
|
|
267
|
+
registerEvalForRuntime(runtime, activeModelId, cellManager);
|
|
268
|
+
});
|
|
255
269
|
}
|
|
256
270
|
|
|
257
271
|
function hostLine(): string {
|
|
@@ -195,7 +195,7 @@ export class JavaScriptKernel {
|
|
|
195
195
|
this.#slot.postMessage({
|
|
196
196
|
type: "run",
|
|
197
197
|
cellId: next.input.cellId,
|
|
198
|
-
code: this.#moduleLoader.prepareCell(next.input.code),
|
|
198
|
+
code: this.#moduleLoader.prepareCell(next.input.code, next.input.kernelPreludes),
|
|
199
199
|
timeoutMs: next.input.timeoutMs,
|
|
200
200
|
});
|
|
201
201
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { join, resolve, sep } from "node:path";
|
|
2
2
|
import { pathToFileURL } from "node:url";
|
|
3
|
+
import type { KernelPreludeContribution } from "@code-yeongyu/senpi";
|
|
3
4
|
import type { BridgeConnectionConfig } from "../../bridge/protocol.ts";
|
|
4
5
|
import {
|
|
5
6
|
RESERVED_AGENT_TOOL,
|
|
@@ -8,6 +9,7 @@ import {
|
|
|
8
9
|
TIMEOUT_PAUSE_OP,
|
|
9
10
|
TIMEOUT_RESUME_OP,
|
|
10
11
|
} from "../../bridge/reserved.ts";
|
|
12
|
+
import { type KernelPreludePlan, KernelPreludeTracker } from "../shared/kernel-prelude-plan.ts";
|
|
11
13
|
import type { JavaScriptKernelOptions as BaseJavaScriptKernelOptions } from "./kernel-contract.ts";
|
|
12
14
|
import { rewriteImports } from "./rewrite-imports.ts";
|
|
13
15
|
|
|
@@ -99,14 +101,26 @@ function loaderPrelude(context: RuntimeModuleContext): string {
|
|
|
99
101
|
].join("\n");
|
|
100
102
|
}
|
|
101
103
|
|
|
104
|
+
/** Removes deactivated exports, then installs each contribution only while one of its exports is missing. */
|
|
105
|
+
function contributionPrelude(plan: KernelPreludePlan): string {
|
|
106
|
+
const removals = plan.remove.map((name) => `delete globalThis[${JSON.stringify(name)}];`);
|
|
107
|
+
const installs = plan.install.map((contribution) => {
|
|
108
|
+
const missing = contribution.exports.map((name) => `!globalThis[${JSON.stringify(name)}]`).join(" || ");
|
|
109
|
+
return `if (${missing}) {\n${contribution.javascript}\n}`;
|
|
110
|
+
});
|
|
111
|
+
return [...removals, ...installs].join("\n");
|
|
112
|
+
}
|
|
113
|
+
|
|
102
114
|
export class LocalModuleLoader {
|
|
103
115
|
readonly #prelude: string;
|
|
116
|
+
readonly #contributions = new KernelPreludeTracker();
|
|
104
117
|
|
|
105
118
|
constructor(options: LocalModuleLoaderOptions) {
|
|
106
119
|
this.#prelude = loaderPrelude(runtimeContext(options));
|
|
107
120
|
}
|
|
108
121
|
|
|
109
|
-
prepareCell(code: string): string {
|
|
110
|
-
|
|
122
|
+
prepareCell(code: string, contributions: readonly KernelPreludeContribution[] = []): string {
|
|
123
|
+
const prelude = `${this.#prelude}\n${contributionPrelude(this.#contributions.plan(contributions))}`;
|
|
124
|
+
return `${PREPARED_CELL_PREFIX}${JSON.stringify({ prelude, code: rewriteImports(code) })}`;
|
|
111
125
|
}
|
|
112
126
|
}
|