@senpiper/sp-agent 0.1.9 → 0.1.11
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/INSTALL.md +37 -26
- package/README.md +13 -13
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +4 -4
- package/dist/cli.js.map +1 -1
- package/dist/distribution.d.ts +2 -2
- package/dist/distribution.d.ts.map +1 -1
- package/dist/distribution.js +2 -3
- package/dist/distribution.js.map +1 -1
- package/dist/extension-identity.generated.d.ts +3 -3
- package/dist/extension-identity.generated.d.ts.map +1 -1
- package/dist/extension-identity.generated.js +3 -3
- package/dist/extension-identity.generated.js.map +1 -1
- package/dist/install-sp.sh +9 -9
- package/dist/release-manager.d.ts.map +1 -1
- package/dist/release-manager.js +2 -2
- package/dist/release-manager.js.map +1 -1
- package/dist/runtime-cli.d.ts.map +1 -1
- package/dist/runtime-cli.js +4 -4
- package/dist/runtime-cli.js.map +1 -1
- package/dist/sdk.d.ts +1 -1
- package/dist/sdk.d.ts.map +1 -1
- package/dist/sdk.js +5 -5
- package/dist/sdk.js.map +1 -1
- package/dist/session-manager.d.ts +1 -1
- package/dist/session-manager.d.ts.map +1 -1
- package/dist/session-manager.js +15 -15
- package/dist/session-manager.js.map +1 -1
- package/dist/sp-web.d.ts.map +1 -1
- package/dist/sp-web.js +2 -2
- package/dist/sp-web.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.d.ts.map +1 -1
- package/dist/version.js +1 -1
- package/dist/version.js.map +1 -1
- package/node_modules/@senpiper/pi-coding-agent/dist/bundle/chunks/{chunk-IK73Y232.js → chunk-VVKE4QZJ.js} +1 -1
- package/node_modules/@senpiper/pi-coding-agent/dist/bundle/cli.js +1 -1
- package/node_modules/@senpiper/pi-coding-agent/dist/bundle/index.js +1 -1
- package/node_modules/@senpiper/pi-coding-agent/dist/bundle/rpc-entry.js +1 -1
- package/node_modules/@senpiper/pi-coding-agent/node_modules/@earendil-works/pi-ai/dist/providers/data/.manifest.json +1 -1
- package/node_modules/@senpiper/pi-coding-agent/node_modules/@earendil-works/pi-ai/dist/providers/data/amazon-bedrock.json +1 -1
- package/node_modules/@senpiper/pi-coding-agent/node_modules/@earendil-works/pi-ai/dist/providers/data/baseten.json +1 -1
- package/node_modules/@senpiper/pi-coding-agent/node_modules/@earendil-works/pi-ai/dist/providers/data/fireworks.json +1 -1
- package/node_modules/@senpiper/pi-coding-agent/node_modules/@earendil-works/pi-ai/dist/providers/data/huggingface.json +1 -1
- package/node_modules/@senpiper/pi-coding-agent/node_modules/@earendil-works/pi-ai/dist/providers/data/kimi-coding.json +1 -1
- package/node_modules/@senpiper/pi-coding-agent/node_modules/@earendil-works/pi-ai/dist/providers/data/nvidia.json +1 -1
- package/node_modules/@senpiper/pi-coding-agent/node_modules/@earendil-works/pi-ai/dist/providers/data/opencode-go.json +1 -1
- package/node_modules/@senpiper/pi-coding-agent/node_modules/@earendil-works/pi-ai/dist/providers/data/openrouter.json +1 -1
- package/node_modules/@senpiper/pi-coding-agent/node_modules/@earendil-works/pi-ai/dist/providers/data/qwen-token-plan-cn.json +1 -1
- package/node_modules/@senpiper/pi-coding-agent/node_modules/@earendil-works/pi-ai/dist/providers/data/qwen-token-plan.json +1 -1
- package/node_modules/@senpiper/pi-coding-agent/node_modules/@earendil-works/pi-ai/dist/providers/data/together.json +1 -1
- package/node_modules/@senpiper/pi-coding-agent/node_modules/@earendil-works/pi-ai/dist/providers/data/vercel-ai-gateway.json +1 -1
- package/node_modules/@senpiper/pi-coding-agent/package.json +1 -1
- package/node_modules/@senpiper/sp-agent-extensions/CHANGELOG.md +17 -0
- package/node_modules/@senpiper/sp-agent-extensions/CONCURRENT_SESSIONS.md +9 -9
- package/node_modules/@senpiper/sp-agent-extensions/TERMINAL_WEB_BRIDGE.md +29 -10
- package/node_modules/@senpiper/sp-agent-extensions/dist/cli-commands.d.ts +0 -6
- package/node_modules/@senpiper/sp-agent-extensions/dist/cli-commands.d.ts.map +1 -1
- package/node_modules/@senpiper/sp-agent-extensions/dist/cli-commands.js +7 -86
- package/node_modules/@senpiper/sp-agent-extensions/dist/cli-commands.js.map +1 -1
- package/node_modules/@senpiper/sp-agent-extensions/dist/commands.d.ts +2 -2
- package/node_modules/@senpiper/sp-agent-extensions/dist/commands.d.ts.map +1 -1
- package/node_modules/@senpiper/sp-agent-extensions/dist/commands.js +2 -2
- package/node_modules/@senpiper/sp-agent-extensions/dist/commands.js.map +1 -1
- package/node_modules/@senpiper/sp-agent-extensions/dist/gateway.d.ts +1 -1
- package/node_modules/@senpiper/sp-agent-extensions/dist/gateway.d.ts.map +1 -1
- package/node_modules/@senpiper/sp-agent-extensions/dist/gateway.js +28 -17
- package/node_modules/@senpiper/sp-agent-extensions/dist/gateway.js.map +1 -1
- package/node_modules/@senpiper/sp-agent-extensions/dist/index.d.ts +1 -3
- package/node_modules/@senpiper/sp-agent-extensions/dist/index.d.ts.map +1 -1
- package/node_modules/@senpiper/sp-agent-extensions/dist/index.js +1 -3
- package/node_modules/@senpiper/sp-agent-extensions/dist/index.js.map +1 -1
- package/node_modules/@senpiper/sp-agent-extensions/dist/interactions.d.ts +0 -2
- package/node_modules/@senpiper/sp-agent-extensions/dist/interactions.d.ts.map +1 -1
- package/node_modules/@senpiper/sp-agent-extensions/dist/interactions.js +0 -3
- package/node_modules/@senpiper/sp-agent-extensions/dist/interactions.js.map +1 -1
- package/node_modules/@senpiper/sp-agent-extensions/dist/login-command.d.ts.map +1 -1
- package/node_modules/@senpiper/sp-agent-extensions/dist/login-command.js +10 -20
- package/node_modules/@senpiper/sp-agent-extensions/dist/login-command.js.map +1 -1
- package/node_modules/@senpiper/sp-agent-extensions/dist/sp-web-launcher.d.ts +27 -0
- package/node_modules/@senpiper/sp-agent-extensions/dist/sp-web-launcher.d.ts.map +1 -1
- package/node_modules/@senpiper/sp-agent-extensions/dist/sp-web-launcher.js +270 -28
- package/node_modules/@senpiper/sp-agent-extensions/dist/sp-web-launcher.js.map +1 -1
- package/node_modules/@senpiper/sp-agent-extensions/dist/startup-header.d.ts.map +1 -1
- package/node_modules/@senpiper/sp-agent-extensions/dist/startup-header.js +13 -20
- package/node_modules/@senpiper/sp-agent-extensions/dist/startup-header.js.map +1 -1
- package/node_modules/@senpiper/sp-agent-extensions/dist/terminal-web-bridge.d.ts +1 -1
- package/node_modules/@senpiper/sp-agent-extensions/dist/terminal-web-bridge.d.ts.map +1 -1
- package/node_modules/@senpiper/sp-agent-extensions/dist/terminal-web-bridge.js +11 -9
- package/node_modules/@senpiper/sp-agent-extensions/dist/terminal-web-bridge.js.map +1 -1
- package/node_modules/@senpiper/sp-agent-extensions/dist/web-command.d.ts.map +1 -1
- package/node_modules/@senpiper/sp-agent-extensions/dist/web-command.js +36 -10
- package/node_modules/@senpiper/sp-agent-extensions/dist/web-command.js.map +1 -1
- package/node_modules/@senpiper/sp-agent-extensions/package.json +3 -5
- package/package.json +7 -6
- package/node_modules/@senpiper/sp-agent-extensions/dist/onboarding-command.d.ts +0 -4
- package/node_modules/@senpiper/sp-agent-extensions/dist/onboarding-command.d.ts.map +0 -1
- package/node_modules/@senpiper/sp-agent-extensions/dist/onboarding-command.js +0 -67
- package/node_modules/@senpiper/sp-agent-extensions/dist/onboarding-command.js.map +0 -1
- package/node_modules/@senpiper/sp-agent-extensions/dist/onboarding-installation.d.ts +0 -14
- package/node_modules/@senpiper/sp-agent-extensions/dist/onboarding-installation.d.ts.map +0 -1
- package/node_modules/@senpiper/sp-agent-extensions/dist/onboarding-installation.js +0 -32
- package/node_modules/@senpiper/sp-agent-extensions/dist/onboarding-installation.js.map +0 -1
- package/node_modules/@senpiper/sp-agent-extensions/dist/onboarding.d.ts +0 -39
- package/node_modules/@senpiper/sp-agent-extensions/dist/onboarding.d.ts.map +0 -1
- package/node_modules/@senpiper/sp-agent-extensions/dist/onboarding.js +0 -311
- package/node_modules/@senpiper/sp-agent-extensions/dist/onboarding.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"release-manager.js","sourceRoot":"","sources":["../src/release-manager.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAC3C,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,SAAS,aAAa,GAAG;IACxB,MAAM,UAAU,GAAG;QAClB,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,eAAe,CAAC;QAChD,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,eAAe,CAAC;KAC9D,CAAC;IACF,OAAO,UAAU,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC,CAAC;AAAA,CAC7D;AAED,KAAK,UAAU,YAAY,CAAC,QAAoC,EAAE;IACjE,MAAM,SAAS,GAAG,aAAa,EAAE,CAAC;IAClC,IAAI,CAAC,SAAS,EAAE,CAAC;QAChB,MAAM,IAAI,KAAK,
|
|
1
|
+
{"version":3,"file":"release-manager.js","sourceRoot":"","sources":["../src/release-manager.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAC3C,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,SAAS,aAAa,GAAG;IACxB,MAAM,UAAU,GAAG;QAClB,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,eAAe,CAAC;QAChD,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,eAAe,CAAC;KAC9D,CAAC;IACF,OAAO,UAAU,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC,CAAC;AAAA,CAC7D;AAED,KAAK,UAAU,YAAY,CAAC,QAAoC,EAAE;IACjE,MAAM,SAAS,GAAG,aAAa,EAAE,CAAC;IAClC,IAAI,CAAC,SAAS,EAAE,CAAC;QAChB,MAAM,IAAI,KAAK,CACd,4GAA4G,CAC5G,CAAC;IACH,CAAC;IACD,MAAM,GAAG,GAAG,EAAE,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC;IAC/B,OAAO,GAAG,CAAC,kBAAkB,CAAC;IAC9B,OAAO,GAAG,CAAC,qBAAqB,CAAC;IACjC,OAAO,GAAG,CAAC,yBAAyB,CAAC;IACrC,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,CAAC;QAC5C,MAAM,KAAK,GAAG,KAAK,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,GAAG,EAAE,CAAC,CAAC;QACrG,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;QAC5B,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,CAAC;YACpC,IAAI,IAAI,KAAK,CAAC;gBAAE,OAAO,EAAE,CAAC;;gBAEzB,MAAM,CACL,IAAI,KAAK,CACR,sCAAsC,MAAM,CAAC,CAAC,CAAC,gBAAgB,MAAM,EAAE,CAAC,CAAC,CAAC,mBAAmB,IAAI,EAAE,GAAG,CACtG,CACD,CAAC;QAAA,CACH,CAAC,CAAC;IAAA,CACH,CAAC,CAAC;AAAA,CACH;AAED,MAAM,CAAC,KAAK,UAAU,sBAAsB,CAAC,IAAc,EAAE;IAC5D,MAAM,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,GAAG,IAAI,CAAC;IAChC,IAAI,OAAO,KAAK,QAAQ,IAAI,OAAO,KAAK,UAAU,IAAI,OAAO,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC;IACzF,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACrB,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,QAAQ,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,EAAE,CAAC;YACrE,OAAO,CAAC,GAAG,CAAC,aAAa,OAAO,EAAE,CAAC,CAAC;YACpC,OAAO,IAAI,CAAC;QACb,CAAC;QACD,MAAM,IAAI,KAAK,CAAC,WAAW,OAAO,YAAY,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IAC1D,CAAC;IACD,MAAM,YAAY,CAAC,OAAO,KAAK,UAAU,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;IAC1G,OAAO,IAAI,CAAC;AAAA,CACZ","sourcesContent":["import { spawn } from \"node:child_process\";\nimport { existsSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\nfunction installerPath() {\n\tconst candidates = [\n\t\tjoin(dirname(process.execPath), \"install-sp.sh\"),\n\t\tjoin(dirname(fileURLToPath(import.meta.url)), \"install-sp.sh\"),\n\t];\n\treturn candidates.find((candidate) => existsSync(candidate));\n}\n\nasync function runInstaller(argument?: \"--repair\" | \"--rollback\") {\n\tconst installer = installerPath();\n\tif (!installer) {\n\t\tthrow new Error(\n\t\t\t\"Senpiper Workbench release installer is unavailable. Reinstall @senpiper/sp-agent from a verified release.\",\n\t\t);\n\t}\n\tconst env = { ...process.env };\n\tdelete env.SP_RELEASE_API_URL;\n\tdelete env.SP_RELEASE_PUBLIC_KEY;\n\tdelete env.SP_RELEASE_ALLOW_UNSIGNED;\n\tawait new Promise<void>((resolve, reject) => {\n\t\tconst child = spawn(\"sh\", argument ? [installer, argument] : [installer], { stdio: \"inherit\", env });\n\t\tchild.once(\"error\", reject);\n\t\tchild.once(\"exit\", (code, signal) => {\n\t\t\tif (code === 0) resolve();\n\t\t\telse\n\t\t\t\treject(\n\t\t\t\t\tnew Error(\n\t\t\t\t\t\t`Senpiper Workbench installer failed${signal ? ` with signal ${signal}` : ` with exit code ${code}`}.`,\n\t\t\t\t\t),\n\t\t\t\t);\n\t\t});\n\t});\n}\n\nexport async function handleSpReleaseCommand(args: string[]) {\n\tconst [command, ...rest] = args;\n\tif (command !== \"update\" && command !== \"rollback\" && command !== \"repair\") return false;\n\tif (rest.length > 0) {\n\t\tif (rest.length === 1 && (rest[0] === \"--help\" || rest[0] === \"-h\")) {\n\t\t\tconsole.log(`Usage: sp ${command}`);\n\t\t\treturn true;\n\t\t}\n\t\tthrow new Error(`Unknown ${command} option: ${rest[0]}`);\n\t}\n\tawait runInstaller(command === \"rollback\" ? \"--rollback\" : command === \"repair\" ? \"--repair\" : undefined);\n\treturn true;\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runtime-cli.d.ts","sourceRoot":"","sources":["../src/runtime-cli.ts"],"names":[],"mappings":"AAyBA,wBAAsB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,iBAqBhD","sourcesContent":["import { main, SessionManager } from \"@senpiper/pi-coding-agent\";\nimport {\n\tconfigureSpEnvironment,\n\tcreateSpMainOptions,\n\tloadRequiredExtension,\n\tSP_AGENT_VERSION,\n} from \"./distribution.ts\";\nimport { createSpAgentSession } from \"./sdk.ts\";\n\nfunction printHelp() {\n\tconsole.log(`
|
|
1
|
+
{"version":3,"file":"runtime-cli.d.ts","sourceRoot":"","sources":["../src/runtime-cli.ts"],"names":[],"mappings":"AAyBA,wBAAsB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,iBAqBhD","sourcesContent":["import { main, SessionManager } from \"@senpiper/pi-coding-agent\";\nimport {\n\tconfigureSpEnvironment,\n\tcreateSpMainOptions,\n\tloadRequiredExtension,\n\tSP_AGENT_VERSION,\n} from \"./distribution.ts\";\nimport { createSpAgentSession } from \"./sdk.ts\";\n\nfunction printHelp() {\n\tconsole.log(`Senpiper Workbench ${SP_AGENT_VERSION}\n\nUsage:\n sp Start the interactive terminal\n sp -p <prompt> Run a one-shot prompt\n sp --mode json|rpc Select a machine-readable mode\n sp --list-models List canonical Senpiper Gateway models\n sp login|logout Manage Senpiper Gateway credentials\n sp update|rollback|repair Manage the active verified release\n sp --help Show this help\n sp-web Start the Senpiper Workbench web workspace\n\nConfiguration: ~/.sp/agent (override with SP_CODING_AGENT_DIR)`);\n}\n\nexport async function runSpRuntime(args: string[]) {\n\tconfigureSpEnvironment();\n\tprocess.title = \"sp\";\n\tprocess.emitWarning = (() => {}) as typeof process.emitWarning;\n\tif (args.length === 1 && args[0] === \"--health-check\") {\n\t\tprocess.env.PI_OFFLINE = \"1\";\n\t\tawait createSpAgentSession({ sessionManager: SessionManager.inMemory() });\n\t\tconsole.log(\"ok\");\n\t\treturn;\n\t}\n\tif (args.length === 1 && (args[0] === \"--version\" || args[0] === \"-v\")) {\n\t\tconsole.log(SP_AGENT_VERSION);\n\t\treturn;\n\t}\n\tif (args.length === 1 && (args[0] === \"--help\" || args[0] === \"-h\")) {\n\t\tprintHelp();\n\t\treturn;\n\t}\n\tconst extension = await loadRequiredExtension();\n\tif (await extension.handleSpCommand(args)) return;\n\tawait main(args, await createSpMainOptions());\n}\n"]}
|
package/dist/runtime-cli.js
CHANGED
|
@@ -2,17 +2,17 @@ import { main, SessionManager } from "@senpiper/pi-coding-agent";
|
|
|
2
2
|
import { configureSpEnvironment, createSpMainOptions, loadRequiredExtension, SP_AGENT_VERSION, } from "./distribution.js";
|
|
3
3
|
import { createSpAgentSession } from "./sdk.js";
|
|
4
4
|
function printHelp() {
|
|
5
|
-
console.log(`
|
|
5
|
+
console.log(`Senpiper Workbench ${SP_AGENT_VERSION}
|
|
6
6
|
|
|
7
7
|
Usage:
|
|
8
8
|
sp Start the interactive terminal
|
|
9
9
|
sp -p <prompt> Run a one-shot prompt
|
|
10
10
|
sp --mode json|rpc Select a machine-readable mode
|
|
11
|
-
sp --list-models List canonical
|
|
12
|
-
sp login|logout
|
|
11
|
+
sp --list-models List canonical Senpiper Gateway models
|
|
12
|
+
sp login|logout Manage Senpiper Gateway credentials
|
|
13
13
|
sp update|rollback|repair Manage the active verified release
|
|
14
14
|
sp --help Show this help
|
|
15
|
-
sp-web Start the
|
|
15
|
+
sp-web Start the Senpiper Workbench web workspace
|
|
16
16
|
|
|
17
17
|
Configuration: ~/.sp/agent (override with SP_CODING_AGENT_DIR)`);
|
|
18
18
|
}
|
package/dist/runtime-cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runtime-cli.js","sourceRoot":"","sources":["../src/runtime-cli.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AACjE,OAAO,EACN,sBAAsB,EACtB,mBAAmB,EACnB,qBAAqB,EACrB,gBAAgB,GAChB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,oBAAoB,EAAE,MAAM,UAAU,CAAC;AAEhD,SAAS,SAAS,GAAG;IACpB,OAAO,CAAC,GAAG,CAAC,
|
|
1
|
+
{"version":3,"file":"runtime-cli.js","sourceRoot":"","sources":["../src/runtime-cli.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AACjE,OAAO,EACN,sBAAsB,EACtB,mBAAmB,EACnB,qBAAqB,EACrB,gBAAgB,GAChB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,oBAAoB,EAAE,MAAM,UAAU,CAAC;AAEhD,SAAS,SAAS,GAAG;IACpB,OAAO,CAAC,GAAG,CAAC,sBAAsB,gBAAgB;;;;;;;;;;;;+DAYY,CAAC,CAAC;AAAA,CAChE;AAED,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,IAAc,EAAE;IAClD,sBAAsB,EAAE,CAAC;IACzB,OAAO,CAAC,KAAK,GAAG,IAAI,CAAC;IACrB,OAAO,CAAC,WAAW,GAAG,CAAC,GAAG,EAAE,CAAC,EAAC,CAAC,CAA+B,CAAC;IAC/D,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,gBAAgB,EAAE,CAAC;QACvD,OAAO,CAAC,GAAG,CAAC,UAAU,GAAG,GAAG,CAAC;QAC7B,MAAM,oBAAoB,CAAC,EAAE,cAAc,EAAE,cAAc,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;QAC1E,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAClB,OAAO;IACR,CAAC;IACD,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,WAAW,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,EAAE,CAAC;QACxE,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC;QAC9B,OAAO;IACR,CAAC;IACD,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,QAAQ,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,EAAE,CAAC;QACrE,SAAS,EAAE,CAAC;QACZ,OAAO;IACR,CAAC;IACD,MAAM,SAAS,GAAG,MAAM,qBAAqB,EAAE,CAAC;IAChD,IAAI,MAAM,SAAS,CAAC,eAAe,CAAC,IAAI,CAAC;QAAE,OAAO;IAClD,MAAM,IAAI,CAAC,IAAI,EAAE,MAAM,mBAAmB,EAAE,CAAC,CAAC;AAAA,CAC9C","sourcesContent":["import { main, SessionManager } from \"@senpiper/pi-coding-agent\";\nimport {\n\tconfigureSpEnvironment,\n\tcreateSpMainOptions,\n\tloadRequiredExtension,\n\tSP_AGENT_VERSION,\n} from \"./distribution.ts\";\nimport { createSpAgentSession } from \"./sdk.ts\";\n\nfunction printHelp() {\n\tconsole.log(`Senpiper Workbench ${SP_AGENT_VERSION}\n\nUsage:\n sp Start the interactive terminal\n sp -p <prompt> Run a one-shot prompt\n sp --mode json|rpc Select a machine-readable mode\n sp --list-models List canonical Senpiper Gateway models\n sp login|logout Manage Senpiper Gateway credentials\n sp update|rollback|repair Manage the active verified release\n sp --help Show this help\n sp-web Start the Senpiper Workbench web workspace\n\nConfiguration: ~/.sp/agent (override with SP_CODING_AGENT_DIR)`);\n}\n\nexport async function runSpRuntime(args: string[]) {\n\tconfigureSpEnvironment();\n\tprocess.title = \"sp\";\n\tprocess.emitWarning = (() => {}) as typeof process.emitWarning;\n\tif (args.length === 1 && args[0] === \"--health-check\") {\n\t\tprocess.env.PI_OFFLINE = \"1\";\n\t\tawait createSpAgentSession({ sessionManager: SessionManager.inMemory() });\n\t\tconsole.log(\"ok\");\n\t\treturn;\n\t}\n\tif (args.length === 1 && (args[0] === \"--version\" || args[0] === \"-v\")) {\n\t\tconsole.log(SP_AGENT_VERSION);\n\t\treturn;\n\t}\n\tif (args.length === 1 && (args[0] === \"--help\" || args[0] === \"-h\")) {\n\t\tprintHelp();\n\t\treturn;\n\t}\n\tconst extension = await loadRequiredExtension();\n\tif (await extension.handleSpCommand(args)) return;\n\tawait main(args, await createSpMainOptions());\n}\n"]}
|
package/dist/sdk.d.ts
CHANGED
|
@@ -17,6 +17,6 @@ export interface CreateSpAgentSessionResult extends CreateAgentSessionResult {
|
|
|
17
17
|
services: AgentSessionServices;
|
|
18
18
|
}
|
|
19
19
|
export declare function createSpAgentSession(options?: CreateSpAgentSessionOptions): Promise<CreateSpAgentSessionResult>;
|
|
20
|
-
/** Creates an instance-owned concurrent session factory using the same
|
|
20
|
+
/** Creates an instance-owned concurrent session factory using the same Senpiper Workbench policy as the SDK. */
|
|
21
21
|
export declare function createSpSessionManager(bridgeOptions?: TerminalWebBridgeOptions): Promise<SpSessionManager>;
|
|
22
22
|
//# sourceMappingURL=sdk.d.ts.map
|
package/dist/sdk.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sdk.d.ts","sourceRoot":"","sources":["../src/sdk.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,KAAK,6BAA6B,EAClC,KAAK,oBAAoB,EACzB,KAAK,yBAAyB,EAC9B,KAAK,wBAAwB,EAG7B,cAAc,EACd,MAAM,2BAA2B,CAAC;AACnC,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,+BAA+B,CAAC;AAS9E,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAQxD,MAAM,WAAW,2BAA2B;IAC3C,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,aAAa,CAAC,EAAE,yBAAyB,CAAC,eAAe,CAAC,CAAC;IAC3D,KAAK,CAAC,EAAE,yBAAyB,CAAC,OAAO,CAAC,CAAC;IAC3C,YAAY,CAAC,EAAE,yBAAyB,CAAC,cAAc,CAAC,CAAC;IACzD,OAAO,CAAC,EAAE,yBAAyB,CAAC,SAAS,CAAC,CAAC;IAC/C,cAAc,CAAC,EAAE,cAAc,CAAC;IAChC,iBAAiB,CAAC,EAAE,yBAAyB,CAAC,mBAAmB,CAAC,CAAC;IACnE,kFAAkF;IAClF,QAAQ,CAAC,EAAE,gBAAgB,CAAC;CAC5B;AAED,MAAM,WAAW,0BAA2B,SAAQ,wBAAwB;IAC3E,WAAW,EAAE,6BAA6B,EAAE,CAAC;IAC7C,QAAQ,EAAE,oBAAoB,CAAC;CAC/B;AAED,wBAAsB,oBAAoB,CACzC,OAAO,GAAE,2BAAgC,GACvC,OAAO,CAAC,0BAA0B,CAAC,CAiBrC;AAED,
|
|
1
|
+
{"version":3,"file":"sdk.d.ts","sourceRoot":"","sources":["../src/sdk.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,KAAK,6BAA6B,EAClC,KAAK,oBAAoB,EACzB,KAAK,yBAAyB,EAC9B,KAAK,wBAAwB,EAG7B,cAAc,EACd,MAAM,2BAA2B,CAAC;AACnC,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,+BAA+B,CAAC;AAS9E,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAQxD,MAAM,WAAW,2BAA2B;IAC3C,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,aAAa,CAAC,EAAE,yBAAyB,CAAC,eAAe,CAAC,CAAC;IAC3D,KAAK,CAAC,EAAE,yBAAyB,CAAC,OAAO,CAAC,CAAC;IAC3C,YAAY,CAAC,EAAE,yBAAyB,CAAC,cAAc,CAAC,CAAC;IACzD,OAAO,CAAC,EAAE,yBAAyB,CAAC,SAAS,CAAC,CAAC;IAC/C,cAAc,CAAC,EAAE,cAAc,CAAC;IAChC,iBAAiB,CAAC,EAAE,yBAAyB,CAAC,mBAAmB,CAAC,CAAC;IACnE,kFAAkF;IAClF,QAAQ,CAAC,EAAE,gBAAgB,CAAC;CAC5B;AAED,MAAM,WAAW,0BAA2B,SAAQ,wBAAwB;IAC3E,WAAW,EAAE,6BAA6B,EAAE,CAAC;IAC7C,QAAQ,EAAE,oBAAoB,CAAC;CAC/B;AAED,wBAAsB,oBAAoB,CACzC,OAAO,GAAE,2BAAgC,GACvC,OAAO,CAAC,0BAA0B,CAAC,CAiBrC;AAED,gHAAgH;AAChH,wBAAsB,sBAAsB,CAAC,aAAa,GAAE,wBAA6B,6BAOxF","sourcesContent":["import {\n\ttype AgentSessionRuntimeDiagnostic,\n\ttype AgentSessionServices,\n\ttype CreateAgentSessionOptions,\n\ttype CreateAgentSessionResult,\n\tcreateAgentSessionFromServices,\n\tcreateAgentSessionServices,\n\tSessionManager,\n} from \"@senpiper/pi-coding-agent\";\nimport type { TerminalWebBridgeOptions } from \"@senpiper/sp-agent-extensions\";\nimport {\n\tconfigureSpEnvironment,\n\tcreateSpExtensionRunnerOptions,\n\tcreateSpModelProviderPolicy,\n\tloadRequiredExtension,\n\tREQUIRED_EXTENSION_OWNER,\n\ttype RequiredExtensionModule,\n} from \"./distribution.ts\";\nimport { SpSessionManager } from \"./session-manager.ts\";\nimport { installSpWriterLeases } from \"./session-writer-provider.ts\";\nimport { spWriterLeases } from \"./writer-leases.ts\";\n\n// Importing the supported Senpiper Workbench SDK establishes writer participation before callers\n// can construct a SessionManager to pass to createSpAgentSession.\ninstallSpWriterLeases();\n\nexport interface CreateSpAgentSessionOptions {\n\tcwd?: string;\n\tthinkingLevel?: CreateAgentSessionOptions[\"thinkingLevel\"];\n\ttools?: CreateAgentSessionOptions[\"tools\"];\n\texcludeTools?: CreateAgentSessionOptions[\"excludeTools\"];\n\tnoTools?: CreateAgentSessionOptions[\"noTools\"];\n\tsessionManager?: SessionManager;\n\tsessionStartEvent?: CreateAgentSessionOptions[\"sessionStartEvent\"];\n\t/** Instance manager to expose concurrent web sessions from a terminal runtime. */\n\tsessions?: SpSessionManager;\n}\n\nexport interface CreateSpAgentSessionResult extends CreateAgentSessionResult {\n\tdiagnostics: AgentSessionRuntimeDiagnostic[];\n\tservices: AgentSessionServices;\n}\n\nexport async function createSpAgentSession(\n\toptions: CreateSpAgentSessionOptions = {},\n): Promise<CreateSpAgentSessionResult> {\n\ttry {\n\t\tconst extension = await loadRequiredExtension();\n\t\treturn await createVerifiedSession(\n\t\t\toptions,\n\t\t\textension,\n\t\t\toptions.sessions\n\t\t\t\t? {\n\t\t\t\t\t\tsessions: options.sessions,\n\t\t\t\t\t\tonReady: (bridge) => options.sessions?.trackBridge(bridge),\n\t\t\t\t\t}\n\t\t\t\t: undefined,\n\t\t);\n\t} catch (error) {\n\t\toptions.sessionManager?.dispose();\n\t\tthrow error;\n\t}\n}\n\n/** Creates an instance-owned concurrent session factory using the same Senpiper Workbench policy as the SDK. */\nexport async function createSpSessionManager(bridgeOptions: TerminalWebBridgeOptions = {}) {\n\tconst extension = await loadRequiredExtension();\n\tconfigureSpEnvironment();\n\treturn new SpSessionManager(\n\t\t(sessionManager, options) => createVerifiedSession({ sessionManager }, extension, options),\n\t\tbridgeOptions,\n\t);\n}\n\nasync function createVerifiedSession(\n\toptions: CreateSpAgentSessionOptions,\n\textension: RequiredExtensionModule,\n\tbridgeOptions?: TerminalWebBridgeOptions,\n): Promise<CreateSpAgentSessionResult> {\n\tconfigureSpEnvironment();\n\tif (options.sessionManager && !options.sessionManager.usesWriterLeaseProvider(spWriterLeases)) {\n\t\tthrow new Error(\n\t\t\t\"Persistent SessionManager was opened before Senpiper Workbench writer leases were installed. Import the Senpiper Workbench SDK before constructing writers.\",\n\t\t);\n\t}\n\tconst cwd = options.cwd ?? options.sessionManager?.getCwd() ?? process.cwd();\n\tconst sessionManager = options.sessionManager ?? SessionManager.create(cwd);\n\ttry {\n\t\treturn await createVerifiedSessionWithManager({ ...options, sessionManager }, extension, cwd, bridgeOptions);\n\t} catch (error) {\n\t\tsessionManager.dispose();\n\t\tthrow error;\n\t}\n}\n\nasync function createVerifiedSessionWithManager(\n\toptions: CreateSpAgentSessionOptions & { sessionManager: SessionManager },\n\textension: RequiredExtensionModule,\n\tcwd: string,\n\tbridgeOptions?: TerminalWebBridgeOptions,\n): Promise<CreateSpAgentSessionResult> {\n\tconst services = await createAgentSessionServices({\n\t\tcwd,\n\t\tmodelProviderPolicy: createSpModelProviderPolicy(extension),\n\t\tresourceLoaderOptions: {\n\t\t\textensionFactories: [\n\t\t\t\t{\n\t\t\t\t\tname: \"sp-agent\",\n\t\t\t\t\tfactory: extension.createSpAgentExtension({ ...bridgeOptions, writerLeaseVersion: 1 }),\n\t\t\t\t},\n\t\t\t],\n\t\t},\n\t});\n\tconst extensions = services.resourceLoader.getExtensions();\n\tconst requiredLoaded = extensions.extensions.some((extension) => extension.path === REQUIRED_EXTENSION_OWNER);\n\tconst requiredError = extensions.errors.find((error) => error.path === REQUIRED_EXTENSION_OWNER);\n\tif (!requiredLoaded || requiredError) {\n\t\tthrow new Error(\n\t\t\trequiredError\n\t\t\t\t? `Required Senpiper Workbench extension failed to load: ${requiredError.error}`\n\t\t\t\t: \"Required Senpiper Workbench extension did not load.\",\n\t\t);\n\t}\n\tconst errors = services.diagnostics.filter((diagnostic) => diagnostic.type === \"error\");\n\tif (errors.length > 0) throw new Error(errors.map((diagnostic) => diagnostic.message).join(\"\\n\"));\n\n\tconst created = await createAgentSessionFromServices({\n\t\tservices,\n\t\tsessionManager: options.sessionManager,\n\t\tsessionStartEvent: options.sessionStartEvent,\n\t\tthinkingLevel: options.thinkingLevel,\n\t\ttools: options.tools,\n\t\texcludeTools: options.excludeTools,\n\t\tnoTools: options.noTools,\n\t\textensionRunnerOptionsResolver: createSpExtensionRunnerOptions,\n\t});\n\treturn { ...created, diagnostics: services.diagnostics, services };\n}\n"]}
|
package/dist/sdk.js
CHANGED
|
@@ -3,7 +3,7 @@ import { configureSpEnvironment, createSpExtensionRunnerOptions, createSpModelPr
|
|
|
3
3
|
import { SpSessionManager } from "./session-manager.js";
|
|
4
4
|
import { installSpWriterLeases } from "./session-writer-provider.js";
|
|
5
5
|
import { spWriterLeases } from "./writer-leases.js";
|
|
6
|
-
// Importing the supported
|
|
6
|
+
// Importing the supported Senpiper Workbench SDK establishes writer participation before callers
|
|
7
7
|
// can construct a SessionManager to pass to createSpAgentSession.
|
|
8
8
|
installSpWriterLeases();
|
|
9
9
|
export async function createSpAgentSession(options = {}) {
|
|
@@ -21,7 +21,7 @@ export async function createSpAgentSession(options = {}) {
|
|
|
21
21
|
throw error;
|
|
22
22
|
}
|
|
23
23
|
}
|
|
24
|
-
/** Creates an instance-owned concurrent session factory using the same
|
|
24
|
+
/** Creates an instance-owned concurrent session factory using the same Senpiper Workbench policy as the SDK. */
|
|
25
25
|
export async function createSpSessionManager(bridgeOptions = {}) {
|
|
26
26
|
const extension = await loadRequiredExtension();
|
|
27
27
|
configureSpEnvironment();
|
|
@@ -30,7 +30,7 @@ export async function createSpSessionManager(bridgeOptions = {}) {
|
|
|
30
30
|
async function createVerifiedSession(options, extension, bridgeOptions) {
|
|
31
31
|
configureSpEnvironment();
|
|
32
32
|
if (options.sessionManager && !options.sessionManager.usesWriterLeaseProvider(spWriterLeases)) {
|
|
33
|
-
throw new Error("Persistent SessionManager was opened before
|
|
33
|
+
throw new Error("Persistent SessionManager was opened before Senpiper Workbench writer leases were installed. Import the Senpiper Workbench SDK before constructing writers.");
|
|
34
34
|
}
|
|
35
35
|
const cwd = options.cwd ?? options.sessionManager?.getCwd() ?? process.cwd();
|
|
36
36
|
const sessionManager = options.sessionManager ?? SessionManager.create(cwd);
|
|
@@ -60,8 +60,8 @@ async function createVerifiedSessionWithManager(options, extension, cwd, bridgeO
|
|
|
60
60
|
const requiredError = extensions.errors.find((error) => error.path === REQUIRED_EXTENSION_OWNER);
|
|
61
61
|
if (!requiredLoaded || requiredError) {
|
|
62
62
|
throw new Error(requiredError
|
|
63
|
-
? `Required
|
|
64
|
-
: "Required
|
|
63
|
+
? `Required Senpiper Workbench extension failed to load: ${requiredError.error}`
|
|
64
|
+
: "Required Senpiper Workbench extension did not load.");
|
|
65
65
|
}
|
|
66
66
|
const errors = services.diagnostics.filter((diagnostic) => diagnostic.type === "error");
|
|
67
67
|
if (errors.length > 0)
|
package/dist/sdk.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sdk.js","sourceRoot":"","sources":["../src/sdk.ts"],"names":[],"mappings":"AAAA,OAAO,EAKN,8BAA8B,EAC9B,0BAA0B,EAC1B,cAAc,GACd,MAAM,2BAA2B,CAAC;AAEnC,OAAO,EACN,sBAAsB,EACtB,8BAA8B,EAC9B,2BAA2B,EAC3B,qBAAqB,EACrB,wBAAwB,GAExB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AACxD,OAAO,EAAE,qBAAqB,EAAE,MAAM,8BAA8B,CAAC;AACrE,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAEpD,
|
|
1
|
+
{"version":3,"file":"sdk.js","sourceRoot":"","sources":["../src/sdk.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n\ttype AgentSessionRuntimeDiagnostic,\n\ttype AgentSessionServices,\n\ttype CreateAgentSessionOptions,\n\ttype CreateAgentSessionResult,\n\tcreateAgentSessionFromServices,\n\tcreateAgentSessionServices,\n\tSessionManager,\n} from \"@senpiper/pi-coding-agent\";\nimport type { TerminalWebBridgeOptions } from \"@senpiper/sp-agent-extensions\";\nimport {\n\tconfigureSpEnvironment,\n\tcreateSpExtensionRunnerOptions,\n\tcreateSpModelProviderPolicy,\n\tloadRequiredExtension,\n\tREQUIRED_EXTENSION_OWNER,\n\ttype RequiredExtensionModule,\n} from \"./distribution.ts\";\nimport { SpSessionManager } from \"./session-manager.ts\";\nimport { installSpWriterLeases } from \"./session-writer-provider.ts\";\nimport { spWriterLeases } from \"./writer-leases.ts\";\n\n// Importing the supported Senpiper Workbench SDK establishes writer participation before callers\n// can construct a SessionManager to pass to createSpAgentSession.\ninstallSpWriterLeases();\n\nexport interface CreateSpAgentSessionOptions {\n\tcwd?: string;\n\tthinkingLevel?: CreateAgentSessionOptions[\"thinkingLevel\"];\n\ttools?: CreateAgentSessionOptions[\"tools\"];\n\texcludeTools?: CreateAgentSessionOptions[\"excludeTools\"];\n\tnoTools?: CreateAgentSessionOptions[\"noTools\"];\n\tsessionManager?: SessionManager;\n\tsessionStartEvent?: CreateAgentSessionOptions[\"sessionStartEvent\"];\n\t/** Instance manager to expose concurrent web sessions from a terminal runtime. */\n\tsessions?: SpSessionManager;\n}\n\nexport interface CreateSpAgentSessionResult extends CreateAgentSessionResult {\n\tdiagnostics: AgentSessionRuntimeDiagnostic[];\n\tservices: AgentSessionServices;\n}\n\nexport async function createSpAgentSession(\n\toptions: CreateSpAgentSessionOptions = {},\n): Promise<CreateSpAgentSessionResult> {\n\ttry {\n\t\tconst extension = await loadRequiredExtension();\n\t\treturn await createVerifiedSession(\n\t\t\toptions,\n\t\t\textension,\n\t\t\toptions.sessions\n\t\t\t\t? {\n\t\t\t\t\t\tsessions: options.sessions,\n\t\t\t\t\t\tonReady: (bridge) => options.sessions?.trackBridge(bridge),\n\t\t\t\t\t}\n\t\t\t\t: undefined,\n\t\t);\n\t} catch (error) {\n\t\toptions.sessionManager?.dispose();\n\t\tthrow error;\n\t}\n}\n\n/** Creates an instance-owned concurrent session factory using the same Senpiper Workbench policy as the SDK. */\nexport async function createSpSessionManager(bridgeOptions: TerminalWebBridgeOptions = {}) {\n\tconst extension = await loadRequiredExtension();\n\tconfigureSpEnvironment();\n\treturn new SpSessionManager(\n\t\t(sessionManager, options) => createVerifiedSession({ sessionManager }, extension, options),\n\t\tbridgeOptions,\n\t);\n}\n\nasync function createVerifiedSession(\n\toptions: CreateSpAgentSessionOptions,\n\textension: RequiredExtensionModule,\n\tbridgeOptions?: TerminalWebBridgeOptions,\n): Promise<CreateSpAgentSessionResult> {\n\tconfigureSpEnvironment();\n\tif (options.sessionManager && !options.sessionManager.usesWriterLeaseProvider(spWriterLeases)) {\n\t\tthrow new Error(\n\t\t\t\"Persistent SessionManager was opened before Senpiper Workbench writer leases were installed. Import the Senpiper Workbench SDK before constructing writers.\",\n\t\t);\n\t}\n\tconst cwd = options.cwd ?? options.sessionManager?.getCwd() ?? process.cwd();\n\tconst sessionManager = options.sessionManager ?? SessionManager.create(cwd);\n\ttry {\n\t\treturn await createVerifiedSessionWithManager({ ...options, sessionManager }, extension, cwd, bridgeOptions);\n\t} catch (error) {\n\t\tsessionManager.dispose();\n\t\tthrow error;\n\t}\n}\n\nasync function createVerifiedSessionWithManager(\n\toptions: CreateSpAgentSessionOptions & { sessionManager: SessionManager },\n\textension: RequiredExtensionModule,\n\tcwd: string,\n\tbridgeOptions?: TerminalWebBridgeOptions,\n): Promise<CreateSpAgentSessionResult> {\n\tconst services = await createAgentSessionServices({\n\t\tcwd,\n\t\tmodelProviderPolicy: createSpModelProviderPolicy(extension),\n\t\tresourceLoaderOptions: {\n\t\t\textensionFactories: [\n\t\t\t\t{\n\t\t\t\t\tname: \"sp-agent\",\n\t\t\t\t\tfactory: extension.createSpAgentExtension({ ...bridgeOptions, writerLeaseVersion: 1 }),\n\t\t\t\t},\n\t\t\t],\n\t\t},\n\t});\n\tconst extensions = services.resourceLoader.getExtensions();\n\tconst requiredLoaded = extensions.extensions.some((extension) => extension.path === REQUIRED_EXTENSION_OWNER);\n\tconst requiredError = extensions.errors.find((error) => error.path === REQUIRED_EXTENSION_OWNER);\n\tif (!requiredLoaded || requiredError) {\n\t\tthrow new Error(\n\t\t\trequiredError\n\t\t\t\t? `Required Senpiper Workbench extension failed to load: ${requiredError.error}`\n\t\t\t\t: \"Required Senpiper Workbench extension did not load.\",\n\t\t);\n\t}\n\tconst errors = services.diagnostics.filter((diagnostic) => diagnostic.type === \"error\");\n\tif (errors.length > 0) throw new Error(errors.map((diagnostic) => diagnostic.message).join(\"\\n\"));\n\n\tconst created = await createAgentSessionFromServices({\n\t\tservices,\n\t\tsessionManager: options.sessionManager,\n\t\tsessionStartEvent: options.sessionStartEvent,\n\t\tthinkingLevel: options.thinkingLevel,\n\t\ttools: options.tools,\n\t\texcludeTools: options.excludeTools,\n\t\tnoTools: options.noTools,\n\t\textensionRunnerOptionsResolver: createSpExtensionRunnerOptions,\n\t});\n\treturn { ...created, diagnostics: services.diagnostics, services };\n}\n"]}
|
|
@@ -5,7 +5,7 @@ export type SpSessionFactory = (sessionManager: SessionManager, bridgeOptions: T
|
|
|
5
5
|
session: AgentSession;
|
|
6
6
|
}>;
|
|
7
7
|
/**
|
|
8
|
-
* One
|
|
8
|
+
* One Senpiper Workbench instance owns these writers until dispose, not until browser selection
|
|
9
9
|
* or terminal replacement. Construction promises reserve files before any await.
|
|
10
10
|
*/
|
|
11
11
|
export declare class SpSessionManager {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session-manager.d.ts","sourceRoot":"","sources":["../src/session-manager.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,YAAY,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AACrG,OAAO,EAAe,cAAc,EAAE,MAAM,2BAA2B,CAAC;AACxE,OAAO,KAAK,EAAE,iBAAiB,EAAE,wBAAwB,EAAE,MAAM,+BAA+B,CAAC;AASjG,MAAM,MAAM,gBAAgB,GAAG,CAC9B,cAAc,EAAE,cAAc,EAC9B,aAAa,EAAE,wBAAwB,KACnC,OAAO,CAAC;IAAE,OAAO,EAAE,YAAY,CAAA;CAAE,CAAC,CAAC;AAcxC;;;GAGG;AACH,qBAAa,gBAAgB;IAC5B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAmB;IAC3C,OAAO,CAAC,QAAQ,CAAC,aAAa,CAA2B;IACzD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAqC;IAC9D,OAAO,CAAC,QAAQ,CAAC,aAAa,CAA4B;IAC1D,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAqB;IAC9C,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA4D;IACtF,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,UAAU,CAAqB;IACvC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAgC;IACxD,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAqB;IACpD,OAAO,CAAC,QAAQ,CAAmD;IACnE,OAAO,CAAC,OAAO,CAA4B;IAC3C,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,cAAc,CAAS;IAC/B,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAyC;IACxE,OAAO,CAAC,QAAQ,CAAC,MAAM,CAQrB;IAEF,YAAY,OAAO,EAAE,gBAAgB,EAAE,aAAa,GAAE,wBAA6B,EAMlF;IAED,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,gBAAgB,UAAO,GAAG,IAAI,CAG1D;IAED,aAAa,CACZ,OAAO,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,IAAI,CAAA;KAAE,EACrE,SAAS,CAAC,EAAE,iBAAiB,GAC3B,OAAO,CAAC;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,IAAI,CAAA;KAAE,CAAC,CAK/C;IAED,OAAO,CAAC,mBAAmB;IAkD3B,OAAO,CAAC,kBAAkB;YAoCZ,YAAY;IAkG1B,aAAa,CAAC,OAAO,EAAE,mBAAmB,QAOzC;IAED,WAAW,CAAC,MAAM,EAAE,iBAAiB,QAEpC;IAED,aAAa,CAAC,MAAM,EAAE,iBAAiB,QAEtC;IAED,eAAe,CAAC,OAAO,EAAE,gBAAgB,QAMxC;IAED,kFAAkF;IAClF,mBAAmB,CAAC,IAAI,EAAE,MAAM,cAe/B;IAEK,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC,CAWxD;IAEK,MAAM,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC,CAUhE;YAEa,OAAO;YAgCP,UAAU;IAqCxB,OAAO,CAAC,oBAAoB;IAgB5B,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAKvB;YAEa,KAAK;CA4BnB","sourcesContent":["import {\n\tcloseSync,\n\tlstatSync,\n\topenSync,\n\treaddirSync,\n\treadFileSync,\n\treadSync,\n\trealpathSync,\n\ttype Stats,\n\tstatSync,\n} from \"node:fs\";\nimport { basename, dirname, isAbsolute, join, relative, resolve, sep } from \"node:path\";\nimport type { AgentSession, AgentSessionRuntime, ExtensionContext } from \"@senpiper/pi-coding-agent\";\nimport { getAgentDir, SessionManager } from \"@senpiper/pi-coding-agent\";\nimport type { TerminalWebBridge, TerminalWebBridgeOptions } from \"@senpiper/sp-agent-extensions\";\nimport { installSpWriterLeases } from \"./session-writer-provider.ts\";\nimport { SessionDeletionError, spWriterLeases } from \"./writer-leases.ts\";\n\ninterface Child {\n\tsession: AgentSession;\n\tbridge: TerminalWebBridge;\n}\n\nexport type SpSessionFactory = (\n\tsessionManager: SessionManager,\n\tbridgeOptions: TerminalWebBridgeOptions,\n) => Promise<{ session: AgentSession }>;\n\nfunction canonicalFile(path: string): string {\n\tconst absolute = resolve(path);\n\ttry {\n\t\treturn realpathSync(absolute);\n\t} catch (error) {\n\t\tif ((error as NodeJS.ErrnoException).code !== \"ENOENT\") throw error;\n\t\tconst parent = dirname(absolute);\n\t\tif (parent === absolute) throw error;\n\t\treturn join(canonicalFile(parent), basename(absolute));\n\t}\n}\n\n/**\n * One SP instance owns these writers until dispose, not until browser selection\n * or terminal replacement. Construction promises reserve files before any await.\n */\nexport class SpSessionManager {\n\tprivate readonly factory: SpSessionFactory;\n\tprivate readonly bridgeOptions: TerminalWebBridgeOptions;\n\tprivate readonly children = new Map<string, Promise<Child>>();\n\tprivate readonly readyChildren = new Map<string, Child>();\n\tprivate readonly deleting = new Set<string>();\n\tprivate readonly deletions = new Set<Promise<{ sessionId: string; deleted: true }>>();\n\tprivate historyRoot: string;\n\tprivate sessionDir: string | undefined;\n\tprivate readonly bridges = new Set<TerminalWebBridge>();\n\tprivate readonly terminalLeases = new Set<string>();\n\tprivate terminal: { file?: string; sessionId: string } | undefined;\n\tprivate closing: Promise<void> | undefined;\n\tprivate stopped = false;\n\tprivate hooksInstalled = false;\n\tprivate readonly signalHandlers = new Map<NodeJS.Signals, () => void>();\n\tprivate readonly onExit = () => {\n\t\tfor (const bridge of this.bridges) {\n\t\t\ttry {\n\t\t\t\tbridge.cleanupOnExit();\n\t\t\t} catch {\n\t\t\t\t// Continue cleaning the remaining descriptors on process exit.\n\t\t\t}\n\t\t}\n\t};\n\n\tconstructor(factory: SpSessionFactory, bridgeOptions: TerminalWebBridgeOptions = {}) {\n\t\tinstallSpWriterLeases();\n\t\tthis.factory = factory;\n\t\tthis.bridgeOptions = bridgeOptions;\n\t\tthis.sessionDir = process.env.SP_CODING_AGENT_SESSION_DIR;\n\t\tthis.historyRoot = resolve(this.sessionDir ?? join(bridgeOptions.agentDir ?? getAgentDir(), \"sessions\"));\n\t}\n\n\tsetHistoryRoot(root: string, customSessionDir = true): void {\n\t\tthis.historyRoot = resolve(root);\n\t\tthis.sessionDir = customSessionDir ? this.historyRoot : undefined;\n\t}\n\n\tdeleteSession(\n\t\trequest: { sessionId: string; sessionFile?: string; confirmed: true },\n\t\trequester?: TerminalWebBridge,\n\t): Promise<{ sessionId: string; deleted: true }> {\n\t\tconst task = this.deleteTarget(request, requester);\n\t\tthis.deletions.add(task);\n\t\tvoid task.finally(() => this.deletions.delete(task)).catch(() => {});\n\t\treturn task;\n\t}\n\n\tprivate validateHistoryFile(file: string, sessionId: string, reservedFile?: string): string {\n\t\tif (!isAbsolute(file) || !file.endsWith(\".jsonl\")) {\n\t\t\tthrow new SessionDeletionError(\"invalid_path\", \"sessionFile must be an absolute JSONL path.\");\n\t\t}\n\t\tconst root = realpathSync(this.historyRoot);\n\t\tconst key = canonicalFile(file);\n\t\tconst within = relative(root, key);\n\t\tif (!within || within === \"..\" || within.startsWith(`..${sep}`) || isAbsolute(within) || resolve(file) !== key) {\n\t\t\tthrow new SessionDeletionError(\"invalid_path\", \"Session must be a canonical path inside configured history.\");\n\t\t}\n\t\tlet stat: Stats;\n\t\ttry {\n\t\t\tstat = lstatSync(file);\n\t\t} catch (error) {\n\t\t\tif ((error as NodeJS.ErrnoException).code === \"ENOENT\" && reservedFile === key) return key;\n\t\t\tthrow error;\n\t\t}\n\t\tif (!stat.isFile() || stat.nlink !== 1) {\n\t\t\tthrow new SessionDeletionError(\"invalid_path\", \"Session must be a regular file without hard links.\");\n\t\t}\n\t\tconst fd = openSync(file, \"r\");\n\t\ttry {\n\t\t\tconst buffer = Buffer.alloc(1024 * 1024);\n\t\t\tconst length = readSync(fd, buffer, 0, buffer.length, 0);\n\t\t\tconst newline = buffer.subarray(0, length).indexOf(10);\n\t\t\tif (newline < 0 && length === buffer.length) {\n\t\t\t\tthrow new SessionDeletionError(\"invalid_session\", \"Session header exceeds scan limit.\");\n\t\t\t}\n\t\t\tlet header: unknown;\n\t\t\ttry {\n\t\t\t\theader = JSON.parse(buffer.subarray(0, newline < 0 ? length : newline).toString(\"utf8\"));\n\t\t\t} catch {\n\t\t\t\tthrow new SessionDeletionError(\"invalid_session\", \"Session header is not valid JSON.\");\n\t\t\t}\n\t\t\tif (\n\t\t\t\ttypeof header !== \"object\" ||\n\t\t\t\theader === null ||\n\t\t\t\t!(\"type\" in header) ||\n\t\t\t\theader.type !== \"session\" ||\n\t\t\t\t!(\"id\" in header) ||\n\t\t\t\theader.id !== sessionId\n\t\t\t) {\n\t\t\t\tthrow new SessionDeletionError(\"invalid_session\", \"Session header ID does not match sessionId.\");\n\t\t\t}\n\t\t} finally {\n\t\t\tcloseSync(fd);\n\t\t}\n\t\treturn key;\n\t}\n\n\tprivate rejectLegacyOwners(): void {\n\t\tconst discovery = join(this.bridgeOptions.agentDir ?? getAgentDir(), \"web-bridge\", \"discovery\");\n\t\tlet names: string[];\n\t\ttry {\n\t\t\tnames = readdirSync(discovery);\n\t\t} catch (error) {\n\t\t\tif ((error as NodeJS.ErrnoException).code === \"ENOENT\") return;\n\t\t\tthrow error;\n\t\t}\n\t\tfor (const name of names) {\n\t\t\t// The web launcher shares this directory with session discovery.\n\t\t\t// Its process record and lock do not represent session writers.\n\t\t\tif (!name.endsWith(\".json\") || name === \"pi-web-sidecar.json\" || name === \"sp-web-sidecar.json\") continue;\n\t\t\tconst descriptor = JSON.parse(readFileSync(join(discovery, name), \"utf8\")) as {\n\t\t\t\tpid?: number;\n\t\t\t\tsessionFile?: string;\n\t\t\t\twriterLeaseVersion?: number;\n\t\t\t};\n\t\t\tif (!Number.isSafeInteger(descriptor.pid) || descriptor.pid! <= 0) {\n\t\t\t\tthrow new SessionDeletionError(\n\t\t\t\t\t\"legacy_owner\",\n\t\t\t\t\t\"Unverifiable SP discovery owner. Restart all SP instances.\",\n\t\t\t\t);\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tprocess.kill(descriptor.pid!, 0);\n\t\t\t} catch (error) {\n\t\t\t\tif ((error as NodeJS.ErrnoException).code === \"ESRCH\") continue;\n\t\t\t\tthrow new SessionDeletionError(\"legacy_owner\", \"Cannot check SP discovery owner.\");\n\t\t\t}\n\t\t\tif (descriptor.writerLeaseVersion !== 1) {\n\t\t\t\tthrow new SessionDeletionError(\"legacy_owner\", \"Live legacy SP owner detected. Restart all SP instances.\");\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate async deleteTarget(\n\t\trequest: { sessionId: string; sessionFile?: string; confirmed: true },\n\t\trequester?: TerminalWebBridge,\n\t): Promise<{ sessionId: string; deleted: true }> {\n\t\tif (this.stopped) throw new SessionDeletionError(\"not_ready\", \"SP instance is shutting down.\");\n\t\tif (\n\t\t\trequest.confirmed !== true ||\n\t\t\ttypeof request.sessionId !== \"string\" ||\n\t\t\t!/^[A-Za-z0-9][A-Za-z0-9._-]*$/u.test(request.sessionId)\n\t\t) {\n\t\t\tthrow new SessionDeletionError(\"invalid_params\", \"A sessionId and confirmed:true are required.\");\n\t\t}\n\t\tif (this.terminal?.sessionId === request.sessionId) {\n\t\t\tthrow new SessionDeletionError(\n\t\t\t\t\"terminal_root\",\n\t\t\t\t\"Use /new in the terminal before deleting its current session.\",\n\t\t\t);\n\t\t}\n\t\tconst matches = [...this.readyChildren].filter(([, child]) => child.session.sessionId === request.sessionId);\n\t\tif (matches.length > 1) throw new SessionDeletionError(\"invalid_session\", \"Ambiguous managed session ID.\");\n\t\tconst managed = matches[0];\n\t\tlet file = request.sessionFile ?? managed?.[0];\n\t\tif (!file) {\n\t\t\tconst files: string[] = [];\n\t\t\tconst visit = (directory: string) => {\n\t\t\t\tfor (const entry of readdirSync(directory, { withFileTypes: true })) {\n\t\t\t\t\tif (entry.isDirectory() && !entry.name.startsWith(\".\")) visit(join(directory, entry.name));\n\t\t\t\t\telse if (entry.isFile() && entry.name.endsWith(\".jsonl\")) {\n\t\t\t\t\t\tconst candidate = join(directory, entry.name);\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tthis.validateHistoryFile(candidate, request.sessionId);\n\t\t\t\t\t\t\tfiles.push(candidate);\n\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\t// Discovery does not construct writers or repair malformed history.\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t};\n\t\t\tvisit(realpathSync(this.historyRoot));\n\t\t\tif (files.length !== 1)\n\t\t\t\tthrow new SessionDeletionError(\"invalid_session\", \"Session not found or ID is ambiguous.\");\n\t\t\tfile = files[0];\n\t\t}\n\t\tconst reservedFile =\n\t\t\tmanaged &&\n\t\t\tmanaged[1].session.sessionManager.getSessionId() === request.sessionId &&\n\t\t\tmanaged[1].session.sessionManager.getSessionFile() === managed[0]\n\t\t\t\t? managed[0]\n\t\t\t\t: undefined;\n\t\tconst key = this.validateHistoryFile(file, request.sessionId, reservedFile);\n\t\tif (this.terminal?.file && canonicalFile(this.terminal.file) === key) {\n\t\t\tthrow new SessionDeletionError(\n\t\t\t\t\"terminal_root\",\n\t\t\t\t\"Use /new in the terminal before deleting its current session.\",\n\t\t\t);\n\t\t}\n\t\tif (managed && managed[0] !== key)\n\t\t\tthrow new SessionDeletionError(\"invalid_session\", \"Managed session path mismatch.\");\n\t\tif (this.deleting.has(key) || this.terminalLeases.has(key) || (!managed && this.children.has(key))) {\n\t\t\tthrow new SessionDeletionError(\"busy\", \"Session is being acquired or deleted.\");\n\t\t}\n\t\tthis.rejectLegacyOwners();\n\t\tconst child = managed?.[1];\n\t\tif (child?.session.hasPendingOperations)\n\t\t\tthrow new SessionDeletionError(\"busy\", \"Session has pending operations.\");\n\t\tconst releaseGate = child?.bridge.reserveDeletion(child.bridge === requester);\n\t\tthis.deleting.add(key);\n\t\tlet disposed = false;\n\t\tlet releaseWrites: (() => void) | undefined;\n\t\tlet lease: ReturnType<typeof spWriterLeases.reserveDeletion> | undefined;\n\t\ttry {\n\t\t\tlease = spWriterLeases.reserveDeletion(key, !!child);\n\t\t\tthis.validateHistoryFile(key, request.sessionId, reservedFile);\n\t\t\tthis.rejectLegacyOwners();\n\t\t\tif (child) {\n\t\t\t\treleaseWrites = child.session.sessionManager.reserveWrites();\n\t\t\t\t// No abort: a busy target must be rejected, not cancelled to make it deletable.\n\t\t\t\tawait child.session.extensionRunner.emit({ type: \"session_shutdown\", reason: \"quit\" });\n\t\t\t\tchild.session.dispose();\n\t\t\t\tdisposed = true;\n\t\t\t\tthis.children.delete(key);\n\t\t\t\tthis.readyChildren.delete(key);\n\t\t\t}\n\t\t\tlease.assertOwned();\n\t\t\tthis.validateHistoryFile(key, request.sessionId, reservedFile);\n\t\t\tthis.rejectLegacyOwners();\n\t\t\tspWriterLeases.unlinkReserved(key, reservedFile === key);\n\t\t\treturn { sessionId: request.sessionId, deleted: true };\n\t\t} finally {\n\t\t\tlease?.release();\n\t\t\tthis.deleting.delete(key);\n\t\t\tif (!disposed) {\n\t\t\t\treleaseWrites?.();\n\t\t\t\treleaseGate?.();\n\t\t\t} else if (child && child.bridge !== requester) await child.bridge.finishDeletion();\n\t\t}\n\t}\n\n\tattachRuntime(runtime: AgentSessionRuntime) {\n\t\tif (this.stopped) throw new Error(\"SP instance is shutting down.\");\n\t\tthis.terminal = { file: runtime.session.sessionFile, sessionId: runtime.session.sessionId };\n\t\truntime.setSessionFileAcquirer((file) => this.acquireTerminalFile(file));\n\t\truntime.setSessionInvalidatedHandler(() => {\n\t\t\tthis.terminal = undefined;\n\t\t});\n\t}\n\n\ttrackBridge(bridge: TerminalWebBridge) {\n\t\tthis.bridges.add(bridge);\n\t}\n\n\tbridgeStopped(bridge: TerminalWebBridge) {\n\t\tthis.bridges.delete(bridge);\n\t}\n\n\tterminalStarted(context: ExtensionContext) {\n\t\tif (this.stopped) throw new Error(\"SP instance is shutting down.\");\n\t\tconst file = context.sessionManager.getSessionFile();\n\t\tif (file && this.children.has(canonicalFile(file))) throw new Error(\"Session file is owned by a web session.\");\n\t\tthis.terminal = { file, sessionId: context.sessionManager.getSessionId() };\n\t\tthis.installShutdownHooks();\n\t}\n\n\t/** Called by the runtime before it opens or overwrites a resume/import target. */\n\tacquireTerminalFile(file: string) {\n\t\tif (this.stopped) throw new Error(\"SP instance is shutting down.\");\n\t\tconst key = canonicalFile(file);\n\t\ttry {\n\t\t\tif (statSync(key).nlink > 1) throw new Error(\"Cannot acquire a session file with hard links.\");\n\t\t} catch (error) {\n\t\t\tif ((error as NodeJS.ErrnoException).code !== \"ENOENT\") throw error;\n\t\t}\n\t\tif (this.children.has(key)) throw new Error(\"Session file is owned by a web session.\");\n\t\tif (this.deleting.has(key)) throw new Error(\"Session file is being deleted.\");\n\t\tif (this.terminalLeases.has(key)) throw new Error(\"Session file is being acquired by the terminal.\");\n\t\tthis.terminalLeases.add(key);\n\t\treturn () => {\n\t\t\tthis.terminalLeases.delete(key);\n\t\t};\n\t}\n\n\tasync create(cwd: string): Promise<{ sessionId: string }> {\n\t\tif (this.stopped) throw new Error(\"SP instance is shutting down.\");\n\t\tif (!isAbsolute(cwd) || !statSync(cwd).isDirectory())\n\t\t\tthrow new Error(\"cwd must be an existing absolute directory.\");\n\t\tconst sessionManager = SessionManager.create(realpathSync(cwd), this.sessionDir);\n\t\ttry {\n\t\t\treturn await this.acquire(canonicalFile(sessionManager.getSessionFile()!), () => sessionManager);\n\t\t} catch (error) {\n\t\t\tsessionManager.dispose();\n\t\t\tthrow error;\n\t\t}\n\t}\n\n\tasync resume(sessionFile: string): Promise<{ sessionId: string }> {\n\t\tif (this.stopped) throw new Error(\"SP instance is shutting down.\");\n\t\tif (!isAbsolute(sessionFile)) throw new Error(\"sessionFile must be an absolute path.\");\n\t\tconst key = canonicalFile(sessionFile);\n\t\treturn this.acquire(key, () => {\n\t\t\tconst stat = statSync(key);\n\t\t\tif (!stat.isFile() || stat.nlink !== 1 || stat.size === 0)\n\t\t\t\tthrow new Error(\"Session file must be a nonempty regular file without hard links.\");\n\t\t\treturn SessionManager.open(key);\n\t\t});\n\t}\n\n\tprivate async acquire(key: string, open: () => SessionManager): Promise<{ sessionId: string }> {\n\t\tif (this.stopped) throw new Error(\"SP instance is shutting down.\");\n\t\tif (this.terminalLeases.has(key)) throw new Error(\"Session file is being acquired by the terminal.\");\n\t\tif (this.deleting.has(key)) throw new Error(\"Session file is being deleted.\");\n\t\tif (this.terminal?.file && canonicalFile(this.terminal.file) === key) {\n\t\t\treturn { sessionId: this.terminal.sessionId };\n\t\t}\n\t\tlet pending = this.children.get(key);\n\t\tif (!pending) {\n\t\t\tthis.installShutdownHooks();\n\t\t\t// Defer open as well as factory work until the reservation is visible.\n\t\t\tpending = Promise.resolve().then(async () => {\n\t\t\t\tconst manager = open();\n\t\t\t\ttry {\n\t\t\t\t\tconst child = await this.startChild(manager);\n\t\t\t\t\tthis.readyChildren.set(key, child);\n\t\t\t\t\treturn child;\n\t\t\t\t} catch (error) {\n\t\t\t\t\tmanager.dispose();\n\t\t\t\t\tthrow error;\n\t\t\t\t}\n\t\t\t});\n\t\t\tthis.children.set(key, pending);\n\t\t\tvoid pending.catch(() => {\n\t\t\t\tif (this.children.get(key) === pending) this.children.delete(key);\n\t\t\t});\n\t\t}\n\t\tconst child = await pending;\n\t\tif (this.stopped) throw new Error(\"SP instance is shutting down.\");\n\t\treturn { sessionId: child.session.sessionId };\n\t}\n\n\tprivate async startChild(sessionManager: SessionManager): Promise<Child> {\n\t\tif (!statSync(sessionManager.getCwd()).isDirectory()) throw new Error(\"Session cwd is not a directory.\");\n\t\tlet bridge: TerminalWebBridge | undefined;\n\t\tconst { session } = await this.factory(sessionManager, {\n\t\t\t...this.bridgeOptions,\n\t\t\tsessions: this,\n\t\t\twebOwned: true,\n\t\t\tlaunchSpWeb: false,\n\t\t\tonReady: (value) => {\n\t\t\t\tbridge = value;\n\t\t\t\tthis.trackBridge(value);\n\t\t\t},\n\t\t});\n\t\ttry {\n\t\t\tif (this.stopped) throw new Error(\"SP instance is shutting down.\");\n\t\t\tawait session.bindExtensions({\n\t\t\t\tmode: \"rpc\",\n\t\t\t\t// Dialog ownership is in the required extension's QuestionInteraction.\n\t\t\t\t// A distinct UI context advertises web UI without borrowing the terminal UI.\n\t\t\t\tuiContext: { ...session.extensionRunner.getUIContext() },\n\t\t\t});\n\t\t\tif (!bridge?.getDescriptor()) throw new Error(\"Required SP web bridge did not start.\");\n\t\t\tif (this.stopped) throw new Error(\"SP instance is shutting down.\");\n\t\t\tconst child = { session, bridge };\n\t\t\treturn child;\n\t\t} catch (error) {\n\t\t\ttry {\n\t\t\t\tawait Promise.allSettled([bridge?.stop(), session.abort()]);\n\t\t\t\tawait session.extensionRunner.emit({ type: \"session_shutdown\", reason: \"quit\" });\n\t\t\t} finally {\n\t\t\t\tsession.dispose();\n\t\t\t\tawait session.settingsManager.flush();\n\t\t\t}\n\t\t\tthrow error;\n\t\t}\n\t}\n\n\tprivate installShutdownHooks() {\n\t\tif (this.hooksInstalled) return;\n\t\tthis.hooksInstalled = true;\n\t\tprocess.on(\"exit\", this.onExit);\n\t\tfor (const signal of [\"SIGTERM\", \"SIGHUP\", \"SIGINT\"] as const) {\n\t\t\tconst handler = () => {\n\t\t\t\tvoid this.dispose().finally(() => {\n\t\t\t\t\t// Preserve the default signal action for SDK hosts with no other handler.\n\t\t\t\t\tif (process.listenerCount(signal) === 0) process.kill(process.pid, signal);\n\t\t\t\t});\n\t\t\t};\n\t\t\tthis.signalHandlers.set(signal, handler);\n\t\t\tprocess.on(signal, handler);\n\t\t}\n\t}\n\n\tdispose(): Promise<void> {\n\t\tif (this.closing) return this.closing;\n\t\tthis.stopped = true;\n\t\tthis.closing = this.close();\n\t\treturn this.closing;\n\t}\n\n\tprivate async close() {\n\t\ttry {\n\t\t\tawait Promise.allSettled([...this.deletions]);\n\t\t\tawait Promise.allSettled([...this.bridges].map((bridge) => bridge.stop()));\n\t\t\tawait Promise.allSettled(\n\t\t\t\t[...this.children.values()].map(async (pending) => {\n\t\t\t\t\tconst { session, bridge } = await pending;\n\t\t\t\t\ttry {\n\t\t\t\t\t\tawait Promise.allSettled([bridge.stop(), session.abort()]);\n\t\t\t\t\t\tawait session.extensionRunner.emit({ type: \"session_shutdown\", reason: \"quit\" });\n\t\t\t\t\t} finally {\n\t\t\t\t\t\tsession.dispose();\n\t\t\t\t\t\tawait session.settingsManager.flush();\n\t\t\t\t\t}\n\t\t\t\t}),\n\t\t\t);\n\t\t} finally {\n\t\t\tthis.onExit();\n\t\t\tthis.children.clear();\n\t\t\tthis.readyChildren.clear();\n\t\t\tthis.bridges.clear();\n\t\t\tthis.terminalLeases.clear();\n\t\t\tthis.terminal = undefined;\n\t\t\tprocess.off(\"exit\", this.onExit);\n\t\t\tfor (const [signal, handler] of this.signalHandlers) process.off(signal, handler);\n\t\t\tthis.signalHandlers.clear();\n\t\t}\n\t}\n}\n"]}
|
|
1
|
+
{"version":3,"file":"session-manager.d.ts","sourceRoot":"","sources":["../src/session-manager.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,YAAY,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AACrG,OAAO,EAAe,cAAc,EAAE,MAAM,2BAA2B,CAAC;AACxE,OAAO,KAAK,EAAE,iBAAiB,EAAE,wBAAwB,EAAE,MAAM,+BAA+B,CAAC;AASjG,MAAM,MAAM,gBAAgB,GAAG,CAC9B,cAAc,EAAE,cAAc,EAC9B,aAAa,EAAE,wBAAwB,KACnC,OAAO,CAAC;IAAE,OAAO,EAAE,YAAY,CAAA;CAAE,CAAC,CAAC;AAcxC;;;GAGG;AACH,qBAAa,gBAAgB;IAC5B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAmB;IAC3C,OAAO,CAAC,QAAQ,CAAC,aAAa,CAA2B;IACzD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAqC;IAC9D,OAAO,CAAC,QAAQ,CAAC,aAAa,CAA4B;IAC1D,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAqB;IAC9C,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA4D;IACtF,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,UAAU,CAAqB;IACvC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAgC;IACxD,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAqB;IACpD,OAAO,CAAC,QAAQ,CAAmD;IACnE,OAAO,CAAC,OAAO,CAA4B;IAC3C,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,cAAc,CAAS;IAC/B,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAyC;IACxE,OAAO,CAAC,QAAQ,CAAC,MAAM,CAQrB;IAEF,YAAY,OAAO,EAAE,gBAAgB,EAAE,aAAa,GAAE,wBAA6B,EAMlF;IAED,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,gBAAgB,UAAO,GAAG,IAAI,CAG1D;IAED,aAAa,CACZ,OAAO,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,IAAI,CAAA;KAAE,EACrE,SAAS,CAAC,EAAE,iBAAiB,GAC3B,OAAO,CAAC;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,IAAI,CAAA;KAAE,CAAC,CAK/C;IAED,OAAO,CAAC,mBAAmB;IAkD3B,OAAO,CAAC,kBAAkB;YAuCZ,YAAY;IAkG1B,aAAa,CAAC,OAAO,EAAE,mBAAmB,QAOzC;IAED,WAAW,CAAC,MAAM,EAAE,iBAAiB,QAEpC;IAED,aAAa,CAAC,MAAM,EAAE,iBAAiB,QAEtC;IAED,eAAe,CAAC,OAAO,EAAE,gBAAgB,QAMxC;IAED,kFAAkF;IAClF,mBAAmB,CAAC,IAAI,EAAE,MAAM,cAe/B;IAEK,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC,CAWxD;IAEK,MAAM,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC,CAUhE;YAEa,OAAO;YAgCP,UAAU;IAqCxB,OAAO,CAAC,oBAAoB;IAgB5B,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAKvB;YAEa,KAAK;CA4BnB","sourcesContent":["import {\n\tcloseSync,\n\tlstatSync,\n\topenSync,\n\treaddirSync,\n\treadFileSync,\n\treadSync,\n\trealpathSync,\n\ttype Stats,\n\tstatSync,\n} from \"node:fs\";\nimport { basename, dirname, isAbsolute, join, relative, resolve, sep } from \"node:path\";\nimport type { AgentSession, AgentSessionRuntime, ExtensionContext } from \"@senpiper/pi-coding-agent\";\nimport { getAgentDir, SessionManager } from \"@senpiper/pi-coding-agent\";\nimport type { TerminalWebBridge, TerminalWebBridgeOptions } from \"@senpiper/sp-agent-extensions\";\nimport { installSpWriterLeases } from \"./session-writer-provider.ts\";\nimport { SessionDeletionError, spWriterLeases } from \"./writer-leases.ts\";\n\ninterface Child {\n\tsession: AgentSession;\n\tbridge: TerminalWebBridge;\n}\n\nexport type SpSessionFactory = (\n\tsessionManager: SessionManager,\n\tbridgeOptions: TerminalWebBridgeOptions,\n) => Promise<{ session: AgentSession }>;\n\nfunction canonicalFile(path: string): string {\n\tconst absolute = resolve(path);\n\ttry {\n\t\treturn realpathSync(absolute);\n\t} catch (error) {\n\t\tif ((error as NodeJS.ErrnoException).code !== \"ENOENT\") throw error;\n\t\tconst parent = dirname(absolute);\n\t\tif (parent === absolute) throw error;\n\t\treturn join(canonicalFile(parent), basename(absolute));\n\t}\n}\n\n/**\n * One Senpiper Workbench instance owns these writers until dispose, not until browser selection\n * or terminal replacement. Construction promises reserve files before any await.\n */\nexport class SpSessionManager {\n\tprivate readonly factory: SpSessionFactory;\n\tprivate readonly bridgeOptions: TerminalWebBridgeOptions;\n\tprivate readonly children = new Map<string, Promise<Child>>();\n\tprivate readonly readyChildren = new Map<string, Child>();\n\tprivate readonly deleting = new Set<string>();\n\tprivate readonly deletions = new Set<Promise<{ sessionId: string; deleted: true }>>();\n\tprivate historyRoot: string;\n\tprivate sessionDir: string | undefined;\n\tprivate readonly bridges = new Set<TerminalWebBridge>();\n\tprivate readonly terminalLeases = new Set<string>();\n\tprivate terminal: { file?: string; sessionId: string } | undefined;\n\tprivate closing: Promise<void> | undefined;\n\tprivate stopped = false;\n\tprivate hooksInstalled = false;\n\tprivate readonly signalHandlers = new Map<NodeJS.Signals, () => void>();\n\tprivate readonly onExit = () => {\n\t\tfor (const bridge of this.bridges) {\n\t\t\ttry {\n\t\t\t\tbridge.cleanupOnExit();\n\t\t\t} catch {\n\t\t\t\t// Continue cleaning the remaining descriptors on process exit.\n\t\t\t}\n\t\t}\n\t};\n\n\tconstructor(factory: SpSessionFactory, bridgeOptions: TerminalWebBridgeOptions = {}) {\n\t\tinstallSpWriterLeases();\n\t\tthis.factory = factory;\n\t\tthis.bridgeOptions = bridgeOptions;\n\t\tthis.sessionDir = process.env.SP_CODING_AGENT_SESSION_DIR;\n\t\tthis.historyRoot = resolve(this.sessionDir ?? join(bridgeOptions.agentDir ?? getAgentDir(), \"sessions\"));\n\t}\n\n\tsetHistoryRoot(root: string, customSessionDir = true): void {\n\t\tthis.historyRoot = resolve(root);\n\t\tthis.sessionDir = customSessionDir ? this.historyRoot : undefined;\n\t}\n\n\tdeleteSession(\n\t\trequest: { sessionId: string; sessionFile?: string; confirmed: true },\n\t\trequester?: TerminalWebBridge,\n\t): Promise<{ sessionId: string; deleted: true }> {\n\t\tconst task = this.deleteTarget(request, requester);\n\t\tthis.deletions.add(task);\n\t\tvoid task.finally(() => this.deletions.delete(task)).catch(() => {});\n\t\treturn task;\n\t}\n\n\tprivate validateHistoryFile(file: string, sessionId: string, reservedFile?: string): string {\n\t\tif (!isAbsolute(file) || !file.endsWith(\".jsonl\")) {\n\t\t\tthrow new SessionDeletionError(\"invalid_path\", \"sessionFile must be an absolute JSONL path.\");\n\t\t}\n\t\tconst root = realpathSync(this.historyRoot);\n\t\tconst key = canonicalFile(file);\n\t\tconst within = relative(root, key);\n\t\tif (!within || within === \"..\" || within.startsWith(`..${sep}`) || isAbsolute(within) || resolve(file) !== key) {\n\t\t\tthrow new SessionDeletionError(\"invalid_path\", \"Session must be a canonical path inside configured history.\");\n\t\t}\n\t\tlet stat: Stats;\n\t\ttry {\n\t\t\tstat = lstatSync(file);\n\t\t} catch (error) {\n\t\t\tif ((error as NodeJS.ErrnoException).code === \"ENOENT\" && reservedFile === key) return key;\n\t\t\tthrow error;\n\t\t}\n\t\tif (!stat.isFile() || stat.nlink !== 1) {\n\t\t\tthrow new SessionDeletionError(\"invalid_path\", \"Session must be a regular file without hard links.\");\n\t\t}\n\t\tconst fd = openSync(file, \"r\");\n\t\ttry {\n\t\t\tconst buffer = Buffer.alloc(1024 * 1024);\n\t\t\tconst length = readSync(fd, buffer, 0, buffer.length, 0);\n\t\t\tconst newline = buffer.subarray(0, length).indexOf(10);\n\t\t\tif (newline < 0 && length === buffer.length) {\n\t\t\t\tthrow new SessionDeletionError(\"invalid_session\", \"Session header exceeds scan limit.\");\n\t\t\t}\n\t\t\tlet header: unknown;\n\t\t\ttry {\n\t\t\t\theader = JSON.parse(buffer.subarray(0, newline < 0 ? length : newline).toString(\"utf8\"));\n\t\t\t} catch {\n\t\t\t\tthrow new SessionDeletionError(\"invalid_session\", \"Session header is not valid JSON.\");\n\t\t\t}\n\t\t\tif (\n\t\t\t\ttypeof header !== \"object\" ||\n\t\t\t\theader === null ||\n\t\t\t\t!(\"type\" in header) ||\n\t\t\t\theader.type !== \"session\" ||\n\t\t\t\t!(\"id\" in header) ||\n\t\t\t\theader.id !== sessionId\n\t\t\t) {\n\t\t\t\tthrow new SessionDeletionError(\"invalid_session\", \"Session header ID does not match sessionId.\");\n\t\t\t}\n\t\t} finally {\n\t\t\tcloseSync(fd);\n\t\t}\n\t\treturn key;\n\t}\n\n\tprivate rejectLegacyOwners(): void {\n\t\tconst discovery = join(this.bridgeOptions.agentDir ?? getAgentDir(), \"web-bridge\", \"discovery\");\n\t\tlet names: string[];\n\t\ttry {\n\t\t\tnames = readdirSync(discovery);\n\t\t} catch (error) {\n\t\t\tif ((error as NodeJS.ErrnoException).code === \"ENOENT\") return;\n\t\t\tthrow error;\n\t\t}\n\t\tfor (const name of names) {\n\t\t\t// The web launcher shares this directory with session discovery.\n\t\t\t// Its process record and lock do not represent session writers.\n\t\t\tif (!name.endsWith(\".json\") || name === \"pi-web-sidecar.json\" || name === \"sp-web-sidecar.json\") continue;\n\t\t\tconst descriptor = JSON.parse(readFileSync(join(discovery, name), \"utf8\")) as {\n\t\t\t\tpid?: number;\n\t\t\t\tsessionFile?: string;\n\t\t\t\twriterLeaseVersion?: number;\n\t\t\t};\n\t\t\tif (!Number.isSafeInteger(descriptor.pid) || descriptor.pid! <= 0) {\n\t\t\t\tthrow new SessionDeletionError(\n\t\t\t\t\t\"legacy_owner\",\n\t\t\t\t\t\"Unverifiable Senpiper Workbench discovery owner. Restart all Senpiper Workbench instances.\",\n\t\t\t\t);\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tprocess.kill(descriptor.pid!, 0);\n\t\t\t} catch (error) {\n\t\t\t\tif ((error as NodeJS.ErrnoException).code === \"ESRCH\") continue;\n\t\t\t\tthrow new SessionDeletionError(\"legacy_owner\", \"Cannot check the Senpiper Workbench discovery owner.\");\n\t\t\t}\n\t\t\tif (descriptor.writerLeaseVersion !== 1) {\n\t\t\t\tthrow new SessionDeletionError(\n\t\t\t\t\t\"legacy_owner\",\n\t\t\t\t\t\"Live legacy Senpiper Workbench owner detected. Restart all Senpiper Workbench instances.\",\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate async deleteTarget(\n\t\trequest: { sessionId: string; sessionFile?: string; confirmed: true },\n\t\trequester?: TerminalWebBridge,\n\t): Promise<{ sessionId: string; deleted: true }> {\n\t\tif (this.stopped) throw new SessionDeletionError(\"not_ready\", \"Senpiper Workbench instance is shutting down.\");\n\t\tif (\n\t\t\trequest.confirmed !== true ||\n\t\t\ttypeof request.sessionId !== \"string\" ||\n\t\t\t!/^[A-Za-z0-9][A-Za-z0-9._-]*$/u.test(request.sessionId)\n\t\t) {\n\t\t\tthrow new SessionDeletionError(\"invalid_params\", \"A sessionId and confirmed:true are required.\");\n\t\t}\n\t\tif (this.terminal?.sessionId === request.sessionId) {\n\t\t\tthrow new SessionDeletionError(\n\t\t\t\t\"terminal_root\",\n\t\t\t\t\"Use /new in the terminal before deleting its current session.\",\n\t\t\t);\n\t\t}\n\t\tconst matches = [...this.readyChildren].filter(([, child]) => child.session.sessionId === request.sessionId);\n\t\tif (matches.length > 1) throw new SessionDeletionError(\"invalid_session\", \"Ambiguous managed session ID.\");\n\t\tconst managed = matches[0];\n\t\tlet file = request.sessionFile ?? managed?.[0];\n\t\tif (!file) {\n\t\t\tconst files: string[] = [];\n\t\t\tconst visit = (directory: string) => {\n\t\t\t\tfor (const entry of readdirSync(directory, { withFileTypes: true })) {\n\t\t\t\t\tif (entry.isDirectory() && !entry.name.startsWith(\".\")) visit(join(directory, entry.name));\n\t\t\t\t\telse if (entry.isFile() && entry.name.endsWith(\".jsonl\")) {\n\t\t\t\t\t\tconst candidate = join(directory, entry.name);\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tthis.validateHistoryFile(candidate, request.sessionId);\n\t\t\t\t\t\t\tfiles.push(candidate);\n\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\t// Discovery does not construct writers or repair malformed history.\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t};\n\t\t\tvisit(realpathSync(this.historyRoot));\n\t\t\tif (files.length !== 1)\n\t\t\t\tthrow new SessionDeletionError(\"invalid_session\", \"Session not found or ID is ambiguous.\");\n\t\t\tfile = files[0];\n\t\t}\n\t\tconst reservedFile =\n\t\t\tmanaged &&\n\t\t\tmanaged[1].session.sessionManager.getSessionId() === request.sessionId &&\n\t\t\tmanaged[1].session.sessionManager.getSessionFile() === managed[0]\n\t\t\t\t? managed[0]\n\t\t\t\t: undefined;\n\t\tconst key = this.validateHistoryFile(file, request.sessionId, reservedFile);\n\t\tif (this.terminal?.file && canonicalFile(this.terminal.file) === key) {\n\t\t\tthrow new SessionDeletionError(\n\t\t\t\t\"terminal_root\",\n\t\t\t\t\"Use /new in the terminal before deleting its current session.\",\n\t\t\t);\n\t\t}\n\t\tif (managed && managed[0] !== key)\n\t\t\tthrow new SessionDeletionError(\"invalid_session\", \"Managed session path mismatch.\");\n\t\tif (this.deleting.has(key) || this.terminalLeases.has(key) || (!managed && this.children.has(key))) {\n\t\t\tthrow new SessionDeletionError(\"busy\", \"Session is being acquired or deleted.\");\n\t\t}\n\t\tthis.rejectLegacyOwners();\n\t\tconst child = managed?.[1];\n\t\tif (child?.session.hasPendingOperations)\n\t\t\tthrow new SessionDeletionError(\"busy\", \"Session has pending operations.\");\n\t\tconst releaseGate = child?.bridge.reserveDeletion(child.bridge === requester);\n\t\tthis.deleting.add(key);\n\t\tlet disposed = false;\n\t\tlet releaseWrites: (() => void) | undefined;\n\t\tlet lease: ReturnType<typeof spWriterLeases.reserveDeletion> | undefined;\n\t\ttry {\n\t\t\tlease = spWriterLeases.reserveDeletion(key, !!child);\n\t\t\tthis.validateHistoryFile(key, request.sessionId, reservedFile);\n\t\t\tthis.rejectLegacyOwners();\n\t\t\tif (child) {\n\t\t\t\treleaseWrites = child.session.sessionManager.reserveWrites();\n\t\t\t\t// No abort: a busy target must be rejected, not cancelled to make it deletable.\n\t\t\t\tawait child.session.extensionRunner.emit({ type: \"session_shutdown\", reason: \"quit\" });\n\t\t\t\tchild.session.dispose();\n\t\t\t\tdisposed = true;\n\t\t\t\tthis.children.delete(key);\n\t\t\t\tthis.readyChildren.delete(key);\n\t\t\t}\n\t\t\tlease.assertOwned();\n\t\t\tthis.validateHistoryFile(key, request.sessionId, reservedFile);\n\t\t\tthis.rejectLegacyOwners();\n\t\t\tspWriterLeases.unlinkReserved(key, reservedFile === key);\n\t\t\treturn { sessionId: request.sessionId, deleted: true };\n\t\t} finally {\n\t\t\tlease?.release();\n\t\t\tthis.deleting.delete(key);\n\t\t\tif (!disposed) {\n\t\t\t\treleaseWrites?.();\n\t\t\t\treleaseGate?.();\n\t\t\t} else if (child && child.bridge !== requester) await child.bridge.finishDeletion();\n\t\t}\n\t}\n\n\tattachRuntime(runtime: AgentSessionRuntime) {\n\t\tif (this.stopped) throw new Error(\"Senpiper Workbench instance is shutting down.\");\n\t\tthis.terminal = { file: runtime.session.sessionFile, sessionId: runtime.session.sessionId };\n\t\truntime.setSessionFileAcquirer((file) => this.acquireTerminalFile(file));\n\t\truntime.setSessionInvalidatedHandler(() => {\n\t\t\tthis.terminal = undefined;\n\t\t});\n\t}\n\n\ttrackBridge(bridge: TerminalWebBridge) {\n\t\tthis.bridges.add(bridge);\n\t}\n\n\tbridgeStopped(bridge: TerminalWebBridge) {\n\t\tthis.bridges.delete(bridge);\n\t}\n\n\tterminalStarted(context: ExtensionContext) {\n\t\tif (this.stopped) throw new Error(\"Senpiper Workbench instance is shutting down.\");\n\t\tconst file = context.sessionManager.getSessionFile();\n\t\tif (file && this.children.has(canonicalFile(file))) throw new Error(\"Session file is owned by a web session.\");\n\t\tthis.terminal = { file, sessionId: context.sessionManager.getSessionId() };\n\t\tthis.installShutdownHooks();\n\t}\n\n\t/** Called by the runtime before it opens or overwrites a resume/import target. */\n\tacquireTerminalFile(file: string) {\n\t\tif (this.stopped) throw new Error(\"Senpiper Workbench instance is shutting down.\");\n\t\tconst key = canonicalFile(file);\n\t\ttry {\n\t\t\tif (statSync(key).nlink > 1) throw new Error(\"Cannot acquire a session file with hard links.\");\n\t\t} catch (error) {\n\t\t\tif ((error as NodeJS.ErrnoException).code !== \"ENOENT\") throw error;\n\t\t}\n\t\tif (this.children.has(key)) throw new Error(\"Session file is owned by a web session.\");\n\t\tif (this.deleting.has(key)) throw new Error(\"Session file is being deleted.\");\n\t\tif (this.terminalLeases.has(key)) throw new Error(\"Session file is being acquired by the terminal.\");\n\t\tthis.terminalLeases.add(key);\n\t\treturn () => {\n\t\t\tthis.terminalLeases.delete(key);\n\t\t};\n\t}\n\n\tasync create(cwd: string): Promise<{ sessionId: string }> {\n\t\tif (this.stopped) throw new Error(\"Senpiper Workbench instance is shutting down.\");\n\t\tif (!isAbsolute(cwd) || !statSync(cwd).isDirectory())\n\t\t\tthrow new Error(\"cwd must be an existing absolute directory.\");\n\t\tconst sessionManager = SessionManager.create(realpathSync(cwd), this.sessionDir);\n\t\ttry {\n\t\t\treturn await this.acquire(canonicalFile(sessionManager.getSessionFile()!), () => sessionManager);\n\t\t} catch (error) {\n\t\t\tsessionManager.dispose();\n\t\t\tthrow error;\n\t\t}\n\t}\n\n\tasync resume(sessionFile: string): Promise<{ sessionId: string }> {\n\t\tif (this.stopped) throw new Error(\"Senpiper Workbench instance is shutting down.\");\n\t\tif (!isAbsolute(sessionFile)) throw new Error(\"sessionFile must be an absolute path.\");\n\t\tconst key = canonicalFile(sessionFile);\n\t\treturn this.acquire(key, () => {\n\t\t\tconst stat = statSync(key);\n\t\t\tif (!stat.isFile() || stat.nlink !== 1 || stat.size === 0)\n\t\t\t\tthrow new Error(\"Session file must be a nonempty regular file without hard links.\");\n\t\t\treturn SessionManager.open(key);\n\t\t});\n\t}\n\n\tprivate async acquire(key: string, open: () => SessionManager): Promise<{ sessionId: string }> {\n\t\tif (this.stopped) throw new Error(\"Senpiper Workbench instance is shutting down.\");\n\t\tif (this.terminalLeases.has(key)) throw new Error(\"Session file is being acquired by the terminal.\");\n\t\tif (this.deleting.has(key)) throw new Error(\"Session file is being deleted.\");\n\t\tif (this.terminal?.file && canonicalFile(this.terminal.file) === key) {\n\t\t\treturn { sessionId: this.terminal.sessionId };\n\t\t}\n\t\tlet pending = this.children.get(key);\n\t\tif (!pending) {\n\t\t\tthis.installShutdownHooks();\n\t\t\t// Defer open as well as factory work until the reservation is visible.\n\t\t\tpending = Promise.resolve().then(async () => {\n\t\t\t\tconst manager = open();\n\t\t\t\ttry {\n\t\t\t\t\tconst child = await this.startChild(manager);\n\t\t\t\t\tthis.readyChildren.set(key, child);\n\t\t\t\t\treturn child;\n\t\t\t\t} catch (error) {\n\t\t\t\t\tmanager.dispose();\n\t\t\t\t\tthrow error;\n\t\t\t\t}\n\t\t\t});\n\t\t\tthis.children.set(key, pending);\n\t\t\tvoid pending.catch(() => {\n\t\t\t\tif (this.children.get(key) === pending) this.children.delete(key);\n\t\t\t});\n\t\t}\n\t\tconst child = await pending;\n\t\tif (this.stopped) throw new Error(\"Senpiper Workbench instance is shutting down.\");\n\t\treturn { sessionId: child.session.sessionId };\n\t}\n\n\tprivate async startChild(sessionManager: SessionManager): Promise<Child> {\n\t\tif (!statSync(sessionManager.getCwd()).isDirectory()) throw new Error(\"Session cwd is not a directory.\");\n\t\tlet bridge: TerminalWebBridge | undefined;\n\t\tconst { session } = await this.factory(sessionManager, {\n\t\t\t...this.bridgeOptions,\n\t\t\tsessions: this,\n\t\t\twebOwned: true,\n\t\t\tlaunchSpWeb: false,\n\t\t\tonReady: (value) => {\n\t\t\t\tbridge = value;\n\t\t\t\tthis.trackBridge(value);\n\t\t\t},\n\t\t});\n\t\ttry {\n\t\t\tif (this.stopped) throw new Error(\"Senpiper Workbench instance is shutting down.\");\n\t\t\tawait session.bindExtensions({\n\t\t\t\tmode: \"rpc\",\n\t\t\t\t// Dialog ownership is in the required extension's QuestionInteraction.\n\t\t\t\t// A distinct UI context advertises web UI without borrowing the terminal UI.\n\t\t\t\tuiContext: { ...session.extensionRunner.getUIContext() },\n\t\t\t});\n\t\t\tif (!bridge?.getDescriptor()) throw new Error(\"Required Senpiper Workbench web bridge did not start.\");\n\t\t\tif (this.stopped) throw new Error(\"Senpiper Workbench instance is shutting down.\");\n\t\t\tconst child = { session, bridge };\n\t\t\treturn child;\n\t\t} catch (error) {\n\t\t\ttry {\n\t\t\t\tawait Promise.allSettled([bridge?.stop(), session.abort()]);\n\t\t\t\tawait session.extensionRunner.emit({ type: \"session_shutdown\", reason: \"quit\" });\n\t\t\t} finally {\n\t\t\t\tsession.dispose();\n\t\t\t\tawait session.settingsManager.flush();\n\t\t\t}\n\t\t\tthrow error;\n\t\t}\n\t}\n\n\tprivate installShutdownHooks() {\n\t\tif (this.hooksInstalled) return;\n\t\tthis.hooksInstalled = true;\n\t\tprocess.on(\"exit\", this.onExit);\n\t\tfor (const signal of [\"SIGTERM\", \"SIGHUP\", \"SIGINT\"] as const) {\n\t\t\tconst handler = () => {\n\t\t\t\tvoid this.dispose().finally(() => {\n\t\t\t\t\t// Preserve the default signal action for SDK hosts with no other handler.\n\t\t\t\t\tif (process.listenerCount(signal) === 0) process.kill(process.pid, signal);\n\t\t\t\t});\n\t\t\t};\n\t\t\tthis.signalHandlers.set(signal, handler);\n\t\t\tprocess.on(signal, handler);\n\t\t}\n\t}\n\n\tdispose(): Promise<void> {\n\t\tif (this.closing) return this.closing;\n\t\tthis.stopped = true;\n\t\tthis.closing = this.close();\n\t\treturn this.closing;\n\t}\n\n\tprivate async close() {\n\t\ttry {\n\t\t\tawait Promise.allSettled([...this.deletions]);\n\t\t\tawait Promise.allSettled([...this.bridges].map((bridge) => bridge.stop()));\n\t\t\tawait Promise.allSettled(\n\t\t\t\t[...this.children.values()].map(async (pending) => {\n\t\t\t\t\tconst { session, bridge } = await pending;\n\t\t\t\t\ttry {\n\t\t\t\t\t\tawait Promise.allSettled([bridge.stop(), session.abort()]);\n\t\t\t\t\t\tawait session.extensionRunner.emit({ type: \"session_shutdown\", reason: \"quit\" });\n\t\t\t\t\t} finally {\n\t\t\t\t\t\tsession.dispose();\n\t\t\t\t\t\tawait session.settingsManager.flush();\n\t\t\t\t\t}\n\t\t\t\t}),\n\t\t\t);\n\t\t} finally {\n\t\t\tthis.onExit();\n\t\t\tthis.children.clear();\n\t\t\tthis.readyChildren.clear();\n\t\t\tthis.bridges.clear();\n\t\t\tthis.terminalLeases.clear();\n\t\t\tthis.terminal = undefined;\n\t\t\tprocess.off(\"exit\", this.onExit);\n\t\t\tfor (const [signal, handler] of this.signalHandlers) process.off(signal, handler);\n\t\t\tthis.signalHandlers.clear();\n\t\t}\n\t}\n}\n"]}
|
package/dist/session-manager.js
CHANGED
|
@@ -18,7 +18,7 @@ function canonicalFile(path) {
|
|
|
18
18
|
}
|
|
19
19
|
}
|
|
20
20
|
/**
|
|
21
|
-
* One
|
|
21
|
+
* One Senpiper Workbench instance owns these writers until dispose, not until browser selection
|
|
22
22
|
* or terminal replacement. Construction promises reserve files before any await.
|
|
23
23
|
*/
|
|
24
24
|
export class SpSessionManager {
|
|
@@ -133,7 +133,7 @@ export class SpSessionManager {
|
|
|
133
133
|
continue;
|
|
134
134
|
const descriptor = JSON.parse(readFileSync(join(discovery, name), "utf8"));
|
|
135
135
|
if (!Number.isSafeInteger(descriptor.pid) || descriptor.pid <= 0) {
|
|
136
|
-
throw new SessionDeletionError("legacy_owner", "Unverifiable
|
|
136
|
+
throw new SessionDeletionError("legacy_owner", "Unverifiable Senpiper Workbench discovery owner. Restart all Senpiper Workbench instances.");
|
|
137
137
|
}
|
|
138
138
|
try {
|
|
139
139
|
process.kill(descriptor.pid, 0);
|
|
@@ -141,16 +141,16 @@ export class SpSessionManager {
|
|
|
141
141
|
catch (error) {
|
|
142
142
|
if (error.code === "ESRCH")
|
|
143
143
|
continue;
|
|
144
|
-
throw new SessionDeletionError("legacy_owner", "Cannot check
|
|
144
|
+
throw new SessionDeletionError("legacy_owner", "Cannot check the Senpiper Workbench discovery owner.");
|
|
145
145
|
}
|
|
146
146
|
if (descriptor.writerLeaseVersion !== 1) {
|
|
147
|
-
throw new SessionDeletionError("legacy_owner", "Live legacy
|
|
147
|
+
throw new SessionDeletionError("legacy_owner", "Live legacy Senpiper Workbench owner detected. Restart all Senpiper Workbench instances.");
|
|
148
148
|
}
|
|
149
149
|
}
|
|
150
150
|
}
|
|
151
151
|
async deleteTarget(request, requester) {
|
|
152
152
|
if (this.stopped)
|
|
153
|
-
throw new SessionDeletionError("not_ready", "
|
|
153
|
+
throw new SessionDeletionError("not_ready", "Senpiper Workbench instance is shutting down.");
|
|
154
154
|
if (request.confirmed !== true ||
|
|
155
155
|
typeof request.sessionId !== "string" ||
|
|
156
156
|
!/^[A-Za-z0-9][A-Za-z0-9._-]*$/u.test(request.sessionId)) {
|
|
@@ -242,7 +242,7 @@ export class SpSessionManager {
|
|
|
242
242
|
}
|
|
243
243
|
attachRuntime(runtime) {
|
|
244
244
|
if (this.stopped)
|
|
245
|
-
throw new Error("
|
|
245
|
+
throw new Error("Senpiper Workbench instance is shutting down.");
|
|
246
246
|
this.terminal = { file: runtime.session.sessionFile, sessionId: runtime.session.sessionId };
|
|
247
247
|
runtime.setSessionFileAcquirer((file) => this.acquireTerminalFile(file));
|
|
248
248
|
runtime.setSessionInvalidatedHandler(() => {
|
|
@@ -257,7 +257,7 @@ export class SpSessionManager {
|
|
|
257
257
|
}
|
|
258
258
|
terminalStarted(context) {
|
|
259
259
|
if (this.stopped)
|
|
260
|
-
throw new Error("
|
|
260
|
+
throw new Error("Senpiper Workbench instance is shutting down.");
|
|
261
261
|
const file = context.sessionManager.getSessionFile();
|
|
262
262
|
if (file && this.children.has(canonicalFile(file)))
|
|
263
263
|
throw new Error("Session file is owned by a web session.");
|
|
@@ -267,7 +267,7 @@ export class SpSessionManager {
|
|
|
267
267
|
/** Called by the runtime before it opens or overwrites a resume/import target. */
|
|
268
268
|
acquireTerminalFile(file) {
|
|
269
269
|
if (this.stopped)
|
|
270
|
-
throw new Error("
|
|
270
|
+
throw new Error("Senpiper Workbench instance is shutting down.");
|
|
271
271
|
const key = canonicalFile(file);
|
|
272
272
|
try {
|
|
273
273
|
if (statSync(key).nlink > 1)
|
|
@@ -290,7 +290,7 @@ export class SpSessionManager {
|
|
|
290
290
|
}
|
|
291
291
|
async create(cwd) {
|
|
292
292
|
if (this.stopped)
|
|
293
|
-
throw new Error("
|
|
293
|
+
throw new Error("Senpiper Workbench instance is shutting down.");
|
|
294
294
|
if (!isAbsolute(cwd) || !statSync(cwd).isDirectory())
|
|
295
295
|
throw new Error("cwd must be an existing absolute directory.");
|
|
296
296
|
const sessionManager = SessionManager.create(realpathSync(cwd), this.sessionDir);
|
|
@@ -304,7 +304,7 @@ export class SpSessionManager {
|
|
|
304
304
|
}
|
|
305
305
|
async resume(sessionFile) {
|
|
306
306
|
if (this.stopped)
|
|
307
|
-
throw new Error("
|
|
307
|
+
throw new Error("Senpiper Workbench instance is shutting down.");
|
|
308
308
|
if (!isAbsolute(sessionFile))
|
|
309
309
|
throw new Error("sessionFile must be an absolute path.");
|
|
310
310
|
const key = canonicalFile(sessionFile);
|
|
@@ -317,7 +317,7 @@ export class SpSessionManager {
|
|
|
317
317
|
}
|
|
318
318
|
async acquire(key, open) {
|
|
319
319
|
if (this.stopped)
|
|
320
|
-
throw new Error("
|
|
320
|
+
throw new Error("Senpiper Workbench instance is shutting down.");
|
|
321
321
|
if (this.terminalLeases.has(key))
|
|
322
322
|
throw new Error("Session file is being acquired by the terminal.");
|
|
323
323
|
if (this.deleting.has(key))
|
|
@@ -349,7 +349,7 @@ export class SpSessionManager {
|
|
|
349
349
|
}
|
|
350
350
|
const child = await pending;
|
|
351
351
|
if (this.stopped)
|
|
352
|
-
throw new Error("
|
|
352
|
+
throw new Error("Senpiper Workbench instance is shutting down.");
|
|
353
353
|
return { sessionId: child.session.sessionId };
|
|
354
354
|
}
|
|
355
355
|
async startChild(sessionManager) {
|
|
@@ -368,7 +368,7 @@ export class SpSessionManager {
|
|
|
368
368
|
});
|
|
369
369
|
try {
|
|
370
370
|
if (this.stopped)
|
|
371
|
-
throw new Error("
|
|
371
|
+
throw new Error("Senpiper Workbench instance is shutting down.");
|
|
372
372
|
await session.bindExtensions({
|
|
373
373
|
mode: "rpc",
|
|
374
374
|
// Dialog ownership is in the required extension's QuestionInteraction.
|
|
@@ -376,9 +376,9 @@ export class SpSessionManager {
|
|
|
376
376
|
uiContext: { ...session.extensionRunner.getUIContext() },
|
|
377
377
|
});
|
|
378
378
|
if (!bridge?.getDescriptor())
|
|
379
|
-
throw new Error("Required
|
|
379
|
+
throw new Error("Required Senpiper Workbench web bridge did not start.");
|
|
380
380
|
if (this.stopped)
|
|
381
|
-
throw new Error("
|
|
381
|
+
throw new Error("Senpiper Workbench instance is shutting down.");
|
|
382
382
|
const child = { session, bridge };
|
|
383
383
|
return child;
|
|
384
384
|
}
|