@code-yeongyu/senpi 2026.10.8 → 2026.10.10-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 +58 -0
- package/dist/bundle/chunks/{app-server-command-MJOYE5ZS.js → app-server-command-WZG7WYMD.js} +1 -1
- package/dist/bundle/chunks/{chunk-MJGHZT4N.js → chunk-3A3C5VVO.js} +1 -1
- package/dist/bundle/chunks/{chunk-4GUQVY4Z.js → chunk-3YXB7Z7O.js} +1 -1
- package/dist/bundle/chunks/{chunk-CB77NQMN.js → chunk-7GVD54RM.js} +1 -1
- package/dist/bundle/chunks/{chunk-6EH7QXOG.js → chunk-CTERKXVZ.js} +2 -2
- package/dist/bundle/chunks/{chunk-CM5QTRC2.js → chunk-H67EVDWG.js} +1 -1
- package/dist/bundle/chunks/chunk-IZOTKUWG.js +2 -0
- package/dist/bundle/chunks/{chunk-K4AZKFRB.js → chunk-J5MUMVWB.js} +2 -2
- package/dist/bundle/chunks/{chunk-V2VQ4BHG.js → chunk-M2IHGL3M.js} +1 -1
- package/dist/bundle/chunks/{chunk-LUWWM7GA.js → chunk-RADHRQ4K.js} +3 -3
- package/dist/bundle/chunks/chunk-RRU5LXMS.js +3 -0
- package/dist/bundle/chunks/{chunk-BTYFICUC.js → chunk-SB22X6ZR.js} +1 -1
- package/dist/bundle/chunks/chunk-ULYOMGXX.js +14 -0
- package/dist/bundle/chunks/{chunk-7Q3EY6HH.js → chunk-UMJL74VN.js} +1 -1
- package/dist/bundle/chunks/{chunk-5MG76BCM.js → chunk-W6B7SEAK.js} +180 -178
- package/dist/bundle/chunks/{cli-main-GWVLFOIP.js → cli-main-T4T5UNRJ.js} +1 -1
- package/dist/bundle/chunks/{help-fast-path-JBQLYO7W.js → help-fast-path-44VUGB7H.js} +1 -1
- package/dist/bundle/chunks/{host-command-HTYYQBSG.js → host-command-X64D7PT3.js} +1 -1
- package/dist/bundle/chunks/{host-lifecycle-XZF6IKKO.js → host-lifecycle-GC3GIR2N.js} +1 -1
- package/dist/bundle/chunks/{interactive-mode-YLM2U7IX.js → interactive-mode-ULFSI46Z.js} +1 -1
- package/dist/bundle/chunks/{list-tips-JQDZPB6C.js → list-tips-IIRFXBTO.js} +1 -1
- package/dist/bundle/chunks/{multi-session-host-KMCBBPTV.js → multi-session-host-VM7WJBC5.js} +1 -1
- package/dist/bundle/chunks/{package-manager-cli-3UIJ4RXY.js → package-manager-cli-OAFUQY7E.js} +1 -1
- package/dist/bundle/chunks/{rpc-mode-PUYBZG27.js → rpc-mode-XVRJJG3K.js} +1 -1
- package/dist/bundle/chunks/{schedule-command-CVAPK2WQ.js → schedule-command-EQ4PQCRP.js} +1 -1
- package/dist/bundle/chunks/{session-control-endpoint-THEF3K7M.js → session-control-endpoint-FEGOYWHN.js} +2 -2
- package/dist/bundle/chunks/{session-picker-N463VCDI.js → session-picker-R5N2EKUS.js} +1 -1
- package/dist/bundle/chunks/session-worker.js +95 -93
- package/dist/bundle/cli.js +1 -1
- package/dist/bundle/index.js +1 -1
- package/dist/bundle/rpc-entry.js +1 -1
- package/dist/bundle/runtime-manifest.json +1 -1
- package/dist/core/agent-session-runtime.d.ts +9 -0
- package/dist/core/agent-session-runtime.js +22 -0
- package/dist/core/agent-session.d.ts +12 -1
- package/dist/core/agent-session.js +38 -4
- package/dist/core/dynamic-prompt/handoff.js +3 -3
- package/dist/core/dynamic-prompt/intent-gate.js +1 -1
- package/dist/core/dynamic-prompt/style.js +1 -1
- package/dist/core/extensions/builtin/btw/panel.d.ts +3 -1
- package/dist/core/extensions/builtin/btw/panel.js +14 -9
- package/dist/core/extensions/builtin/compaction/context-pipeline.d.ts +5 -0
- package/dist/core/extensions/builtin/compaction/context-pipeline.js +1 -0
- package/dist/core/extensions/builtin/compaction/index.js +2 -1
- package/dist/core/extensions/builtin/compaction/lane-policy.d.ts +7 -0
- package/dist/core/extensions/builtin/compaction/lane-policy.js +28 -15
- package/dist/core/extensions/builtin/compaction/log.d.ts +2 -0
- package/dist/core/extensions/builtin/compaction/log.js +2 -1
- package/dist/core/extensions/builtin/goal/cache-warm-renderer.js +14 -8
- package/dist/core/extensions/builtin/goal/cache-warm.d.ts +4 -2
- package/dist/core/extensions/builtin/goal/monitor-continuation.js +6 -0
- package/dist/core/extensions/builtin/mcp/commands.js +21 -26
- package/dist/core/extensions/builtin/mcp/config-edit.d.ts +2 -0
- package/dist/core/extensions/builtin/mcp/config-edit.js +5 -2
- package/dist/core/extensions/builtin/mcp/manager-view.d.ts +34 -0
- package/dist/core/extensions/builtin/mcp/manager-view.js +136 -0
- package/dist/core/extensions/builtin/mcp/manager.d.ts +8 -0
- package/dist/core/extensions/builtin/mcp/manager.js +193 -0
- package/dist/core/extensions/builtin/mcp/service.d.ts +3 -1
- package/dist/core/extensions/builtin/mcp/service.js +4 -0
- package/dist/core/extensions/builtin/permission-system/index.js +4 -0
- package/dist/core/extensions/builtin/permission-system/prompt.js +10 -2
- package/dist/core/extensions/builtin/permission-system/types.d.ts +3 -1
- package/dist/core/extensions/builtin/prompt-preset/claude-fable-5-1.js +2 -2
- package/dist/core/extensions/builtin/prompt-preset/claude-fable-5.js +2 -2
- package/dist/core/extensions/builtin/prompt-preset/claude-opus-5-5.js +2 -2
- package/dist/core/extensions/builtin/prompt-preset/claude-opus-5.js +2 -2
- package/dist/core/extensions/builtin/prompt-preset/claude-sonnet-5-5.js +2 -2
- package/dist/core/extensions/builtin/prompt-preset/gpt-5.5.js +3 -3
- package/dist/core/extensions/builtin/prompt-preset/gpt-5.6.js +3 -3
- package/dist/core/extensions/builtin/prompt-preset/gpt-6-astra.d.ts +2 -2
- package/dist/core/extensions/builtin/prompt-preset/gpt-6-astra.js +4 -4
- package/dist/core/extensions/builtin/prompt-preset/gpt-surface.js +2 -2
- package/dist/core/extensions/builtin/prompt-preset/grok-4.5.js +2 -2
- package/dist/core/extensions/builtin/prompt-preset/grok-4.6.js +1 -1
- package/dist/core/extensions/builtin/prompt-preset/grok-4.7.js +1 -1
- package/dist/core/extensions/builtin/prompt-preset/kimi-k3.js +2 -2
- package/dist/core/extensions/builtin/rules/rules/finder.js +8 -3
- package/dist/core/extensions/types.d.ts +4 -0
- package/dist/core/retry-fallback/expansion.js +9 -3
- package/dist/core/sdk.js +7 -3
- package/dist/core/thinking-levels.d.ts +23 -1
- package/dist/core/thinking-levels.js +37 -0
- package/dist/core/trust-manager.js +50 -0
- package/dist/main.js +12 -13
- package/dist/modes/interactive/interactive-mode.js +8 -0
- package/dist/modes/interactive/session-control-endpoint.js +33 -8
- package/dist/modes/interactive/session-control-registry.js +3 -1
- package/dist/modes/interactive/session-control-wake.d.ts +11 -5
- package/dist/modes/interactive/session-control-wake.js +38 -16
- package/dist/modes/rpc/connection-handler.d.ts +5 -0
- package/dist/modes/rpc/connection-handler.js +41 -5
- package/dist/modes/rpc/custom-capability.d.ts +6 -0
- package/dist/modes/rpc/custom-capability.js +6 -0
- package/dist/modes/rpc/host-daemon-paths.d.ts +5 -4
- package/dist/modes/rpc/host-daemon-paths.js +17 -9
- package/dist/modes/rpc/host-daemon-registration.d.ts +6 -1
- package/dist/modes/rpc/host-daemon-registration.js +6 -2
- package/dist/modes/rpc/host-session-control.js +6 -4
- package/dist/modes/rpc/rpc-client.d.ts +5 -0
- package/dist/modes/rpc/rpc-client.js +7 -0
- package/dist/modes/rpc/rpc-mode.js +1 -1
- package/dist/modes/rpc/rpc-types.d.ts +14 -0
- package/docs/json.md +3 -0
- package/docs/mcp.md +16 -1
- package/docs/rpc.md +49 -1
- package/package.json +6 -6
- package/dist/bundle/chunks/chunk-6SMU7ANE.js +0 -3
- package/dist/bundle/chunks/chunk-VJJ53ORU.js +0 -2
- package/dist/bundle/chunks/chunk-X6UDRO7J.js +0 -14
|
@@ -80,6 +80,12 @@ export declare const BROWSER_ENGINE_CAPABILITY = "browser_engine";
|
|
|
80
80
|
* never written to a settings file and never seen by another session on the host.
|
|
81
81
|
*/
|
|
82
82
|
export declare const RETRY_FALLBACK_PROFILE_CAPABILITY = "retry_fallback_profile";
|
|
83
|
+
/**
|
|
84
|
+
* SINGLE-SESSION capability: this `--mode rpc` process accepts `set_retry_fallback` (the
|
|
85
|
+
* `open_session.retryFallback` shape) before its first turn, so a caller that spawns one process per
|
|
86
|
+
* session can give it its own fallback chain without a settings file.
|
|
87
|
+
*/
|
|
88
|
+
export declare const RETRY_FALLBACK_COMMAND_CAPABILITY = "retry_fallback_command";
|
|
83
89
|
/**
|
|
84
90
|
* HOST capability: `open_session.promptSurface` also accepts `chat` (a chat bridge: no routing line,
|
|
85
91
|
* no handoff block, no todo cues). A host without it refuses `chat` with `invalid_launch_profile`,
|
|
@@ -80,6 +80,12 @@ export const BROWSER_ENGINE_CAPABILITY = "browser_engine";
|
|
|
80
80
|
* never written to a settings file and never seen by another session on the host.
|
|
81
81
|
*/
|
|
82
82
|
export const RETRY_FALLBACK_PROFILE_CAPABILITY = "retry_fallback_profile";
|
|
83
|
+
/**
|
|
84
|
+
* SINGLE-SESSION capability: this `--mode rpc` process accepts `set_retry_fallback` (the
|
|
85
|
+
* `open_session.retryFallback` shape) before its first turn, so a caller that spawns one process per
|
|
86
|
+
* session can give it its own fallback chain without a settings file.
|
|
87
|
+
*/
|
|
88
|
+
export const RETRY_FALLBACK_COMMAND_CAPABILITY = "retry_fallback_command";
|
|
83
89
|
/**
|
|
84
90
|
* HOST capability: `open_session.promptSurface` also accepts `chat` (a chat bridge: no routing line,
|
|
85
91
|
* no handoff block, no todo cues). A host without it refuses `chat` with `invalid_launch_profile`,
|
|
@@ -101,10 +101,11 @@ export declare class HostDaemonStateError extends Error {
|
|
|
101
101
|
constructor(path: string, cause: unknown);
|
|
102
102
|
}
|
|
103
103
|
/**
|
|
104
|
-
* Creates this endpoint's directories and publishes the flat marker.
|
|
105
|
-
*
|
|
106
|
-
*
|
|
107
|
-
*
|
|
104
|
+
* Creates this endpoint's directories and publishes the flat marker. A directory that already exists
|
|
105
|
+
* keeps whatever mode it was created with - and this one holds the evidence that decides who may
|
|
106
|
+
* signal the daemon - so an existing one is re-moded explicitly; one `mkdir` just created already
|
|
107
|
+
* has the mode. The siblings inside the endpoint directory and the marker are written concurrently.
|
|
108
|
+
* A `tui` registrant passes its kind; everything else is an `rpc_host`.
|
|
108
109
|
*/
|
|
109
110
|
export declare function createDaemonDirectories(paths: HostDaemonPaths, identity?: {
|
|
110
111
|
readonly kind?: EndpointKind;
|
|
@@ -159,27 +159,35 @@ export class HostDaemonStateError extends Error {
|
|
|
159
159
|
}
|
|
160
160
|
}
|
|
161
161
|
/**
|
|
162
|
-
* Creates this endpoint's directories and publishes the flat marker.
|
|
163
|
-
*
|
|
164
|
-
*
|
|
165
|
-
*
|
|
162
|
+
* Creates this endpoint's directories and publishes the flat marker. A directory that already exists
|
|
163
|
+
* keeps whatever mode it was created with - and this one holds the evidence that decides who may
|
|
164
|
+
* signal the daemon - so an existing one is re-moded explicitly; one `mkdir` just created already
|
|
165
|
+
* has the mode. The siblings inside the endpoint directory and the marker are written concurrently.
|
|
166
|
+
* A `tui` registrant passes its kind; everything else is an `rpc_host`.
|
|
166
167
|
*/
|
|
167
168
|
export async function createDaemonDirectories(paths, identity = {}) {
|
|
168
169
|
try {
|
|
169
170
|
// The flat directory may predate this layout and may hold a legacy host's files: it is created
|
|
170
171
|
// when missing and never re-moded, so a legacy host keeps whatever it set up for itself.
|
|
171
172
|
await mkdir(paths.flatDir, { recursive: true, mode: DIRECTORY_MODE });
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
173
|
+
await makePrivateDirectory(paths.dir);
|
|
174
|
+
await Promise.all([
|
|
175
|
+
makePrivateDirectory(paths.generationsDir),
|
|
176
|
+
makePrivateDirectory(paths.reservationsDir),
|
|
177
|
+
writeFile(paths.layoutMarker, `${JSON.stringify({ layout: HOST_DAEMON_LAYOUT, dir: basename(paths.dir) })}\n`, { mode: HOST_STATE_FILE_MODE }),
|
|
178
|
+
]);
|
|
177
179
|
}
|
|
178
180
|
catch (cause) {
|
|
179
181
|
throw new HostDaemonStateError(paths.dir, cause);
|
|
180
182
|
}
|
|
181
183
|
await ensureEndpointIdentity(paths, paths.socket, identity);
|
|
182
184
|
}
|
|
185
|
+
/** `mkdir` names the directory it created; only one that already existed needs its mode set. */
|
|
186
|
+
async function makePrivateDirectory(directory) {
|
|
187
|
+
const created = await mkdir(directory, { recursive: true, mode: DIRECTORY_MODE });
|
|
188
|
+
if (created === undefined)
|
|
189
|
+
await chmod(directory, DIRECTORY_MODE);
|
|
190
|
+
}
|
|
183
191
|
/**
|
|
184
192
|
* Writes `endpoint.json` when it is absent and never rewrites a valid one: the first writer's
|
|
185
193
|
* `created_at` is the endpoint's birth. The file is written whole to a temporary name and LINKED
|
|
@@ -40,8 +40,13 @@ export declare function readHostRegistration(paths: HostDaemonPaths): Promise<Re
|
|
|
40
40
|
* Registers a generation and points the daemon directory at it, under this process's writer stamp.
|
|
41
41
|
* The stamp is what authorizes a later stop: only the process that wrote a record may signal the
|
|
42
42
|
* host it names, and the recorded start time keeps a recycled pid from inheriting that right.
|
|
43
|
+
*
|
|
44
|
+
* `fresh` says the directory cannot hold an earlier generation (a terminal's endpoint, named by a
|
|
45
|
+
* socket built from a new instance id), so there is nothing to prune.
|
|
43
46
|
*/
|
|
44
|
-
export declare function writeHostRegistration(paths: HostDaemonPaths, registration: HostRegistration
|
|
47
|
+
export declare function writeHostRegistration(paths: HostDaemonPaths, registration: HostRegistration, options?: {
|
|
48
|
+
readonly fresh?: boolean;
|
|
49
|
+
}): Promise<void>;
|
|
45
50
|
/**
|
|
46
51
|
* Records ONE generation's process in its own directory without moving the pointer. A handoff writes
|
|
47
52
|
* this the moment its successor is spawned: until the rename lands the pointer must keep naming the
|
|
@@ -53,12 +53,16 @@ export async function readHostRegistration(paths) {
|
|
|
53
53
|
* Registers a generation and points the daemon directory at it, under this process's writer stamp.
|
|
54
54
|
* The stamp is what authorizes a later stop: only the process that wrote a record may signal the
|
|
55
55
|
* host it names, and the recorded start time keeps a recycled pid from inheriting that right.
|
|
56
|
+
*
|
|
57
|
+
* `fresh` says the directory cannot hold an earlier generation (a terminal's endpoint, named by a
|
|
58
|
+
* socket built from a new instance id), so there is nothing to prune.
|
|
56
59
|
*/
|
|
57
|
-
export async function writeHostRegistration(paths, registration) {
|
|
60
|
+
export async function writeHostRegistration(paths, registration, options = {}) {
|
|
58
61
|
// Every write is also the moment to drop what is no longer running: records of dead generations
|
|
59
62
|
// and their session-path claims otherwise accumulate for the life of the agent directory, and a
|
|
60
63
|
// stale pointer among them reads as "a daemon serves this endpoint" (#1893).
|
|
61
|
-
|
|
64
|
+
if (options.fresh !== true)
|
|
65
|
+
await pruneDeadGenerations(paths);
|
|
62
66
|
const { generation, writer } = await writeGenerationRecord(paths, registration);
|
|
63
67
|
// The pointer is replaced by rename: a reader either sees the generation that owned the socket
|
|
64
68
|
// before this call or the one that owns it now, never a half-written pointer.
|
|
@@ -21,18 +21,18 @@ export class HostSessionControl {
|
|
|
21
21
|
wake("idle");
|
|
22
22
|
});
|
|
23
23
|
const unsubscribeEmitted = session.externalAdmission.onEmitted(() => wake("emitted"));
|
|
24
|
-
let
|
|
24
|
+
let inbox;
|
|
25
25
|
const registration = {
|
|
26
26
|
scheduler,
|
|
27
27
|
release: () => {
|
|
28
28
|
unsubscribeIdle();
|
|
29
29
|
unsubscribeEmitted();
|
|
30
|
-
|
|
30
|
+
inbox?.stop();
|
|
31
31
|
scheduler.dispose();
|
|
32
32
|
},
|
|
33
33
|
};
|
|
34
34
|
try {
|
|
35
|
-
|
|
35
|
+
inbox = await watchInbox(options.inboxDir, () => wake("inbox"), (error) => this.report(`control inbox watch failed: ${errorText(error)}`));
|
|
36
36
|
}
|
|
37
37
|
catch (error) {
|
|
38
38
|
registration.release();
|
|
@@ -45,8 +45,10 @@ export class HostSessionControl {
|
|
|
45
45
|
}
|
|
46
46
|
this.releaseActive();
|
|
47
47
|
this.active = registration;
|
|
48
|
-
// Anything that reached the inbox before
|
|
48
|
+
// Anything that reached the inbox before this point is picked up by this first pass, and anything
|
|
49
|
+
// written after it but before the watch was armed (no event for it) by the pass once arming settled.
|
|
49
50
|
wake("inbox");
|
|
51
|
+
void inbox.armed.then(() => wake("inbox"));
|
|
50
52
|
return {
|
|
51
53
|
status: "registered",
|
|
52
54
|
socket: this.socket,
|
|
@@ -330,6 +330,11 @@ export declare class RpcClient {
|
|
|
330
330
|
* Abort in-progress retry.
|
|
331
331
|
*/
|
|
332
332
|
abortRetry(): Promise<void>;
|
|
333
|
+
/**
|
|
334
|
+
* Give this single-session process its fallback chain before the first turn (`set_retry_fallback`).
|
|
335
|
+
* Requires the `retry_fallback_command` capability.
|
|
336
|
+
*/
|
|
337
|
+
setRetryFallback(retryFallback: SessionRetryFallbackProfile): Promise<void>;
|
|
333
338
|
/**
|
|
334
339
|
* Execute a bash command.
|
|
335
340
|
*/
|
|
@@ -599,6 +599,13 @@ export class RpcClient {
|
|
|
599
599
|
async abortRetry() {
|
|
600
600
|
await this.send({ type: "abort_retry" });
|
|
601
601
|
}
|
|
602
|
+
/**
|
|
603
|
+
* Give this single-session process its fallback chain before the first turn (`set_retry_fallback`).
|
|
604
|
+
* Requires the `retry_fallback_command` capability.
|
|
605
|
+
*/
|
|
606
|
+
async setRetryFallback(retryFallback) {
|
|
607
|
+
await this.send({ type: "set_retry_fallback", retryFallback });
|
|
608
|
+
}
|
|
602
609
|
/**
|
|
603
610
|
* Execute a bash command.
|
|
604
611
|
*/
|
|
@@ -128,7 +128,7 @@ export async function runRpcMode(runtimeHost) {
|
|
|
128
128
|
// it from a client handshake; a plain stdio client that sets nothing gets
|
|
129
129
|
// byte-identical default behavior.
|
|
130
130
|
const capabilities = parseClientCapabilities(envValue("RPC_CLIENT_CAPABILITIES"));
|
|
131
|
-
const handler = createRpcConnectionHandler(runtimeHost, sink, { capabilities });
|
|
131
|
+
const handler = createRpcConnectionHandler(runtimeHost, sink, { capabilities, retryFallbackCommand: true });
|
|
132
132
|
const signalCleanupHandlers = [];
|
|
133
133
|
const registerSignalHandlers = () => {
|
|
134
134
|
const signals = ["SIGTERM"];
|
|
@@ -173,6 +173,10 @@ type RpcSessionCommand = {
|
|
|
173
173
|
} | {
|
|
174
174
|
id?: string;
|
|
175
175
|
type: "abort_retry";
|
|
176
|
+
} | {
|
|
177
|
+
id?: string;
|
|
178
|
+
type: "set_retry_fallback";
|
|
179
|
+
retryFallback: SessionRetryFallbackProfile;
|
|
176
180
|
} | {
|
|
177
181
|
id?: string;
|
|
178
182
|
type: "bash";
|
|
@@ -991,6 +995,11 @@ export type RpcResponse = {
|
|
|
991
995
|
type: "response";
|
|
992
996
|
command: "abort_retry";
|
|
993
997
|
success: true;
|
|
998
|
+
} | {
|
|
999
|
+
id?: string;
|
|
1000
|
+
type: "response";
|
|
1001
|
+
command: "set_retry_fallback";
|
|
1002
|
+
success: true;
|
|
994
1003
|
} | {
|
|
995
1004
|
id?: string;
|
|
996
1005
|
type: "response";
|
|
@@ -1319,6 +1328,9 @@ export type RpcExtensionUIRequest = {
|
|
|
1319
1328
|
title: string;
|
|
1320
1329
|
options: string[];
|
|
1321
1330
|
timeout?: number;
|
|
1331
|
+
/** The tool call this dialog approves, when it is a permission request. */
|
|
1332
|
+
toolCallId?: string;
|
|
1333
|
+
parentToolCallId?: string;
|
|
1322
1334
|
} | {
|
|
1323
1335
|
type: "extension_ui_request";
|
|
1324
1336
|
id: string;
|
|
@@ -1333,6 +1345,8 @@ export type RpcExtensionUIRequest = {
|
|
|
1333
1345
|
title: string;
|
|
1334
1346
|
placeholder?: string;
|
|
1335
1347
|
timeout?: number;
|
|
1348
|
+
toolCallId?: string;
|
|
1349
|
+
parentToolCallId?: string;
|
|
1336
1350
|
} | {
|
|
1337
1351
|
type: "extension_ui_request";
|
|
1338
1352
|
id: string;
|
package/docs/json.md
CHANGED
|
@@ -22,6 +22,7 @@ type AgentSessionEvent =
|
|
|
22
22
|
| { type: "compaction_end"; reason: "manual" | "threshold" | "overflow"; result: CompactionResult | undefined; aborted: boolean; willRetry: boolean; requestId?: string; accepted?: boolean; rejectionCause?: "cancelled-by-extension" | "external-owner" | "would-overflow" | "circuit-breaker" | "per-turn-cap"; errorMessage?: string }
|
|
23
23
|
| { type: "session_info_changed"; name: string | undefined }
|
|
24
24
|
| { type: "thinking_level_changed"; level: "off" | "minimal" | "low" | "medium" | "high" | "xhigh" | "max" }
|
|
25
|
+
| { type: "thinking_level_clamped"; provider: string; modelId: string; requestedLevel: "off" | "minimal" | "low" | "medium" | "high" | "xhigh" | "max"; appliedLevel: "off" | "minimal" | "low" | "medium" | "high" | "xhigh" | "max"; reason: "model-not-reasoning" | "level-unsupported" }
|
|
25
26
|
| SystemPromptChangeEvent // type: "system_prompt_change"
|
|
26
27
|
| ExtensionToolHookLifecycleEvent // type: "tool_hook_status"
|
|
27
28
|
| { type: "auto_retry_start"; attempt: number; maxAttempts: number; delayMs: number; errorMessage: string }
|
|
@@ -55,6 +56,8 @@ type JsonAgentSessionEvent =
|
|
|
55
56
|
|
|
56
57
|
`queue_update` emits the full pending steering and follow-up queues whenever they change. `compaction_start`, `compaction_progress`, and `compaction_end` cover both manual and automatic compaction. `session_info_changed` fires when the session display name changes, `thinking_level_changed` when the thinking level changes, `system_prompt_change` (see `SystemPromptChangeEvent` in [`extensions/types.ts`](../src/core/extensions/types.ts)) when a model switch changes the active system prompt, and `tool_hook_status` (see `ExtensionToolHookLifecycleEvent` in [`extensions/runner.ts`](../src/core/extensions/runner.ts)) for extension tool hook start/end phases.
|
|
57
58
|
|
|
59
|
+
`thinking_level_clamped` fires once per model and requested level when an explicit thinking request (such as `--thinking`, `/thinking`, or a remembered per-model level) runs at a lower level than asked. A level that only comes from the global `defaultThinkingLevel` does not count as a request. In RPC mode, the `thinkingSelection` carried by `thinking_level_changed` and `get_state` then also has `requested` and `clampReason`.
|
|
60
|
+
|
|
58
61
|
Other base events come from
|
|
59
62
|
[`AgentEvent`](../../agent/src/types.ts):
|
|
60
63
|
|
package/docs/mcp.md
CHANGED
|
@@ -22,12 +22,27 @@ catalog costs almost nothing until the model actually needs it.
|
|
|
22
22
|
}
|
|
23
23
|
```
|
|
24
24
|
|
|
25
|
-
2. Start senpi. Run `/mcp` for the
|
|
25
|
+
2. Start senpi. Run `/mcp` for the interactive server manager, `/mcp status` for a one-line
|
|
26
26
|
summary, `/mcp add <name> <command...>` to add servers interactively.
|
|
27
27
|
3. Servers needing OAuth: `/mcp auth <name>` (see [Auth](#auth)).
|
|
28
28
|
4. Use it: small catalogs register directly; big ones surface through
|
|
29
29
|
`tool_search` (see [Exposure tiers](#exposure-tiers)).
|
|
30
30
|
|
|
31
|
+
## Interactive manager
|
|
32
|
+
|
|
33
|
+
`/mcp` lists servers with their connection state, tool and available resource
|
|
34
|
+
counts, exposure mode, and configuration source. Select a server to inspect its
|
|
35
|
+
tools, details, or logs; test or reconnect it; or sign in/out when OAuth applies.
|
|
36
|
+
The list updates when connections and catalogs change and preserves selection.
|
|
37
|
+
Navigation, confirmation, and cancellation use your configured keybindings.
|
|
38
|
+
|
|
39
|
+
Enable/disable and exposure changes are saved to the selected server's global
|
|
40
|
+
or trusted project `mcp.json`. Imported, skill-owned, extension-owned, and
|
|
41
|
+
untrusted definitions are read-only in this manager. OAuth actions close the
|
|
42
|
+
manager before running the existing authorization flow, keeping its notices
|
|
43
|
+
visible. Outside the TUI, `/mcp` reports status through the existing notification
|
|
44
|
+
channel; existing subcommands remain available.
|
|
45
|
+
|
|
31
46
|
## Configuration reference
|
|
32
47
|
|
|
33
48
|
Top-level shape: `{ "settings": { ... }, "mcpServers": { "<name>": { ... } } }`.
|
package/docs/rpc.md
CHANGED
|
@@ -1924,7 +1924,7 @@ The `model` field is a full [Model](#model) object.
|
|
|
1924
1924
|
|
|
1925
1925
|
#### get_available_models
|
|
1926
1926
|
|
|
1927
|
-
List all configured models.
|
|
1927
|
+
List all configured models, refreshing credential availability on each request so credentials added or removed by another session take effect without reopening this session.
|
|
1928
1928
|
|
|
1929
1929
|
```json
|
|
1930
1930
|
{"type": "get_available_models"}
|
|
@@ -2191,6 +2191,41 @@ Response:
|
|
|
2191
2191
|
{"type": "response", "command": "abort_retry", "success": true}
|
|
2192
2192
|
```
|
|
2193
2193
|
|
|
2194
|
+
#### set_retry_fallback
|
|
2195
|
+
|
|
2196
|
+
A single-session `--mode rpc` process has no `open_session`. A caller that spawns one process per session (e.g. a task
|
|
2197
|
+
child that runs as its own process) gives it its fallback policy with `set_retry_fallback`:
|
|
2198
|
+
|
|
2199
|
+
```json
|
|
2200
|
+
{"type": "set_retry_fallback", "retryFallback": {"modelFallback": true, "fallbackChains": {"provider/model": ["provider/spare"]}}}
|
|
2201
|
+
```
|
|
2202
|
+
|
|
2203
|
+
Response:
|
|
2204
|
+
```json
|
|
2205
|
+
{"type": "response", "command": "set_retry_fallback", "success": true}
|
|
2206
|
+
```
|
|
2207
|
+
|
|
2208
|
+
The profile is the same shape, with the same limits, as `open_session.retryFallback`, and it has the same effect. It
|
|
2209
|
+
overrides the session's `retry.modelFallback` and `retry.fallbackChains` in memory only, it is never written to a
|
|
2210
|
+
settings file, and the process's later sessions (`new_session`, `switch_session`, `fork`) keep it.
|
|
2211
|
+
|
|
2212
|
+
It is a launch-time setting:
|
|
2213
|
+
- It is accepted only before the session's first turn. It is refused once this connection has asked for a turn
|
|
2214
|
+
(`prompt`, `steer`, `follow_up`, `continue_from_leaf`, `send_custom_message` with `triggerTurn`), even one that has
|
|
2215
|
+
not started yet, while a turn streams (an extension's included), and once the session holds turn history (a resumed
|
|
2216
|
+
session's). So a chain never changes under a provider request or a retry already in flight. Context messages added
|
|
2217
|
+
before the first turn (role `custom`, from an extension on `session_start` or a `send_custom_message` without
|
|
2218
|
+
`triggerTurn`) are not a turn and do not block it.
|
|
2219
|
+
- Send it on its own and await its response: a `set_retry_fallback` pipelined while a `new_session`, `switch_session`
|
|
2220
|
+
or `fork` is still being created can answer success while the session being created runs without it.
|
|
2221
|
+
- A malformed profile is refused with the `open_session` shape message, and nothing is applied.
|
|
2222
|
+
- A multi-session host refuses the command on its session connections; a host session takes its policy from
|
|
2223
|
+
`open_session.retryFallback`.
|
|
2224
|
+
|
|
2225
|
+
Probe `retry_fallback_command` in `get_protocol_info` before sending it. Only a single-session process advertises it.
|
|
2226
|
+
Unlike an environment variable, the profile reaches this process only, never what its tools spawn. Unlike argv, it has
|
|
2227
|
+
no command-line length limit and is not visible in a process listing.
|
|
2228
|
+
|
|
2194
2229
|
### Bash
|
|
2195
2230
|
|
|
2196
2231
|
#### bash
|
|
@@ -3562,6 +3597,19 @@ Prompt the user to choose from a list. Dialog methods with a `timeout` field inc
|
|
|
3562
3597
|
|
|
3563
3598
|
Expected response: `extension_ui_response` with `value` (the selected option string) or `cancelled: true`.
|
|
3564
3599
|
|
|
3600
|
+
A permission prompt (title `Permission required: <permission>`) also carries `toolCallId`, the id of the tool call it approves, and `parentToolCallId` when another tool (for example a codemode script) issued that call. Clients bind the prompt to that call by id: the engine raises the prompts for every call of a message before any of them runs, so the order of `tool_execution_start` events does not say which call a prompt belongs to. The `input` that follows a "Deny with feedback" choice carries the same fields.
|
|
3601
|
+
|
|
3602
|
+
```json
|
|
3603
|
+
{
|
|
3604
|
+
"type": "extension_ui_request",
|
|
3605
|
+
"id": "uuid-3",
|
|
3606
|
+
"method": "select",
|
|
3607
|
+
"title": "Permission required: read\n\nPath: /project/a.txt\n\nPatterns:\n - /project/a.txt",
|
|
3608
|
+
"options": ["Allow once", "Allow always", "Deny", "Deny with feedback"],
|
|
3609
|
+
"toolCallId": "call-a"
|
|
3610
|
+
}
|
|
3611
|
+
```
|
|
3612
|
+
|
|
3565
3613
|
#### confirm
|
|
3566
3614
|
|
|
3567
3615
|
Prompt the user for yes/no confirmation.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@code-yeongyu/senpi",
|
|
3
|
-
"version": "2026.10.
|
|
3
|
+
"version": "2026.10.10-2",
|
|
4
4
|
"description": "Coding agent CLI with read, bash, edit, write tools and session management",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"piConfig": {
|
|
@@ -57,9 +57,9 @@
|
|
|
57
57
|
},
|
|
58
58
|
"dependencies": {
|
|
59
59
|
"@earendil-works/chord": "0.99.1",
|
|
60
|
-
"@earendil-works/pi-agent-core": "npm:@code-yeongyu/senpi-agent-core@2026.10.
|
|
61
|
-
"@earendil-works/pi-ai": "npm:@code-yeongyu/senpi-ai@2026.10.
|
|
62
|
-
"@earendil-works/pi-tui": "npm:@code-yeongyu/senpi-tui@2026.10.
|
|
60
|
+
"@earendil-works/pi-agent-core": "npm:@code-yeongyu/senpi-agent-core@2026.10.10-2",
|
|
61
|
+
"@earendil-works/pi-ai": "npm:@code-yeongyu/senpi-ai@2026.10.10-2",
|
|
62
|
+
"@earendil-works/pi-tui": "npm:@code-yeongyu/senpi-tui@2026.10.10-2",
|
|
63
63
|
"@silvia-odwyer/photon-node": "0.3.4",
|
|
64
64
|
"chalk": "6.0.0",
|
|
65
65
|
"cross-spawn": "7.0.6",
|
|
@@ -78,8 +78,8 @@
|
|
|
78
78
|
"yaml": "2.9.1",
|
|
79
79
|
"@anthropic-ai/claude-agent-sdk": "0.3.289",
|
|
80
80
|
"@anthropic-ai/sdk": "0.127.0",
|
|
81
|
-
"@code-yeongyu/senpi-codemode": "2026.10.
|
|
82
|
-
"@earendil-works/pi-pty": "npm:@code-yeongyu/senpi-pty@2026.10.
|
|
81
|
+
"@code-yeongyu/senpi-codemode": "2026.10.10-2",
|
|
82
|
+
"@earendil-works/pi-pty": "npm:@code-yeongyu/senpi-pty@2026.10.10-2",
|
|
83
83
|
"@modelcontextprotocol/sdk": "1.30.0",
|
|
84
84
|
"@mozilla/readability": "0.6.0",
|
|
85
85
|
"linkedom": "0.18.13",
|
|
@@ -1,3 +0,0 @@
|
|
|
1
|
-
import { createRequire as __piCreateRequire } from "node:module"; const require = __piCreateRequire(import.meta.url); { const __piWorkerThreads = require("node:worker_threads"); if (typeof __piWorkerThreads.markAsUncloneable !== "function") { __piWorkerThreads.markAsUncloneable = () => {}; } }
|
|
2
|
-
import{require_proper_lockfile}from"./chunk-GZVCRV7I.js";import{CONFIG_DIR_NAME,canonicalizePath,canonicalizePathStrict,resolvePath,stripBom}from"./chunk-463OK2Y4.js";import{__toESM}from"./chunk-IKOFCODM.js";var import_proper_lockfile=__toESM(require_proper_lockfile(),1);import{existsSync,mkdirSync,readFileSync,writeFileSync}from"node:fs";import{homedir}from"node:os";import{dirname,join}from"node:path";var TRUST_REQUIRING_PROJECT_CONFIG_RESOURCES=["settings.json","mcp.json","extensions","skills","prompts","themes","SYSTEM.md","APPEND_SYSTEM.md"],LEGACY_PROJECT_CONFIG_DIR_NAME=".pi";function normalizeCwd(cwd){let resolved=resolvePath(cwd);try{return canonicalizePathStrict(resolved)}catch{return resolved}}function findNearestTrustEntry(data,cwd){let currentDir=normalizeCwd(cwd);for(;;){let value=data[currentDir];if(value===!0||value===!1)return{path:currentDir,decision:value};let parentDir=dirname(currentDir);if(parentDir===currentDir)return null;currentDir=parentDir}}function getProjectTrustParentPath(cwd){let trustPath=normalizeCwd(cwd),parentDir=dirname(trustPath);return parentDir===trustPath?void 0:parentDir}function getProjectTrustOptions(cwd,options){let trustPath=normalizeCwd(cwd),trustOptions=[{label:"Trust",trusted:!0,updates:[{path:trustPath,decision:!0}],savedPath:trustPath}],parentPath=getProjectTrustParentPath(cwd);return parentPath!==void 0&&trustOptions.push({label:`Trust parent folder (${parentPath})`,trusted:!0,updates:[{path:parentPath,decision:!0},{path:trustPath,decision:null}],savedPath:parentPath}),options?.includeSessionOnly&&trustOptions.push({label:"Trust (this session only)",trusted:!0,updates:[]}),trustOptions.push({label:"Do not trust",trusted:!1,updates:[{path:trustPath,decision:!1}],savedPath:trustPath}),options?.includeSessionOnly&&trustOptions.push({label:"Do not trust (this session only)",trusted:!1,updates:[]}),trustOptions}function readTrustFile(path){if(!existsSync(path))return{};let parsed;try{parsed=JSON.parse(stripBom(readFileSync(path,"utf-8")))}catch(error){let message=error instanceof Error?error.message:String(error);throw new Error(`Failed to read trust store ${path}: ${message}`)}if(typeof parsed!="object"||parsed===null||Array.isArray(parsed))throw new Error(`Invalid trust store ${path}: expected an object`);let data={};for(let[key,value]of Object.entries(parsed)){if(value!==!0&&value!==!1&&value!==null)throw new Error(`Invalid trust store ${path}: value for ${JSON.stringify(key)} must be true, false, or null`);data[key]=value}return data}function writeTrustFile(path,data){let sorted={};for(let key of Object.keys(data).sort()){let value=data[key];(value===!0||value===!1||value===null)&&(sorted[key]=value)}mkdirSync(dirname(path),{recursive:!0}),writeFileSync(path,`${JSON.stringify(sorted,null,2)}
|
|
3
|
-
`,"utf-8")}function acquireTrustLockSync(path){let trustDir=dirname(path);mkdirSync(trustDir,{recursive:!0});let maxAttempts=10,delayMs=20,lastError;for(let attempt=1;attempt<=maxAttempts;attempt++)try{return import_proper_lockfile.default.lockSync(trustDir,{realpath:!1,lockfilePath:`${path}.lock`})}catch(error){if((typeof error=="object"&&error!==null&&"code"in error?String(error.code):void 0)!=="ELOCKED"||attempt===maxAttempts)throw error;lastError=error;let start=Date.now();for(;Date.now()-start<delayMs;);}throw lastError instanceof Error?lastError:new Error("Failed to acquire trust store lock")}function withTrustFileLock(path,fn){let release=acquireTrustLockSync(path);try{return fn()}finally{release()}}function hasTrustRequiringProjectResources(cwd){let homeDir=canonicalizePath(resolvePath(process.env.HOME||homedir())),userAgentsSkillsDir=join(homeDir,".agents","skills"),currentDir=canonicalizePath(resolvePath(cwd));if([join(currentDir,CONFIG_DIR_NAME),join(currentDir,LEGACY_PROJECT_CONFIG_DIR_NAME)].some(configDir=>TRUST_REQUIRING_PROJECT_CONFIG_RESOURCES.some(entry=>existsSync(join(configDir,entry)))))return!0;for(;;){let agentsSkillsDir=join(currentDir,".agents","skills");if(agentsSkillsDir!==userAgentsSkillsDir&&existsSync(agentsSkillsDir))return!0;let parentDir=dirname(currentDir);if(parentDir===currentDir)return!1;currentDir=parentDir}}var ProjectTrustStore=class{constructor(agentDir){this.trustPath=join(resolvePath(agentDir),"trust.json")}get(cwd){return this.getEntry(cwd)?.decision??null}getEntry(cwd){return withTrustFileLock(this.trustPath,()=>{let data=readTrustFile(this.trustPath);return findNearestTrustEntry(data,cwd)})}set(cwd,decision){this.setMany([{path:cwd,decision}])}setMany(decisions){withTrustFileLock(this.trustPath,()=>{let data=readTrustFile(this.trustPath);for(let{path,decision}of decisions){let key=normalizeCwd(path);decision===null?delete data[key]:data[key]=decision}writeTrustFile(this.trustPath,data)})}};export{getProjectTrustOptions,hasTrustRequiringProjectResources,ProjectTrustStore};
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
import { createRequire as __piCreateRequire } from "node:module"; const require = __piCreateRequire(import.meta.url); { const __piWorkerThreads = require("node:worker_threads"); if (typeof __piWorkerThreads.markAsUncloneable !== "function") { __piWorkerThreads.markAsUncloneable = () => {}; } }
|
|
2
|
-
import{engineBuildIdentity}from"./chunk-ZJHRDQCH.js";import{HOST_GENERATION_ENV,HOST_INSTANCE_ID_ENV}from"./chunk-CIRZTP7W.js";import{parseArgs,resolveSessionRuntime}from"./chunk-MH2BFYGN.js";import*as crypto from"node:crypto";var CUSTOM_UNSUPPORTED_CAPABILITY="custom_unsupported",EXTENSION_EVENTS_CAPABILITY="extension_events",AUTO_TITLE_SESSIONS_CAPABILITY="auto_title_sessions",CONTINUE_FROM_LEAF_CAPABILITY="continue_from_leaf",MEDIA_PLACEHOLDERS_CAPABILITY="media_placeholders",QUESTION_CAPABILITY="question",RETAIN_ON_DISCONNECT_CAPABILITY="retain_on_disconnect",SESSION_CONTEXT_CAPABILITY="session_context",SESSION_KIND_CAPABILITY="session_kind",AUTO_TITLE_PER_SESSION_CAPABILITY="auto_title_per_session",WARM_CAPABILITY="warm",DURABLE_SESSION_ID_CAPABILITY="durable_session_id",PROMPT_SURFACE_CAPABILITY="prompt_surface",BROWSER_ENGINE_CAPABILITY="browser_engine",RETRY_FALLBACK_PROFILE_CAPABILITY="retry_fallback_profile",PROMPT_SURFACE_CHAT_CAPABILITY="prompt_surface_chat",RPC_CLIENT_CAPABILITIES_ENV="SENPI_RPC_CLIENT_CAPABILITIES";function parseClientCapabilities(value){return value?value.split(",").map(part=>part.trim()).filter(part=>part.length>0):[]}var DEFAULT_CUSTOM_EXTENSION_LABEL="custom UI component";function buildCustomUnsupportedRequest(capabilities,extensionName){if(!capabilities?.includes(CUSTOM_UNSUPPORTED_CAPABILITY))return;let name=extensionName.trim()||DEFAULT_CUSTOM_EXTENSION_LABEL;return{type:"extension_ui_request",id:crypto.randomUUID(),method:"custom_unsupported",extensionName:name}}import{createHash,randomUUID as randomUUID2}from"node:crypto";import{resolve}from"node:path";var INSTANCE_ID=resolveInstanceId(process.env[HOST_INSTANCE_ID_ENV]);function resolveInstanceId(value){return value!==void 0&&value.trim()!==""?value:randomUUID2()}function hostInstanceId(){return INSTANCE_ID}var cachedProfile;function hostGeneration(env){let value=env[HOST_GENERATION_ENV];return value!==void 0&&/^\d+$/.test(value)?Number(value):0}function hostLaunchProfile(argv,cwd){let parsed=parseArgs([...argv],{grokNeoEnabled:!1}),core={extensions:[...new Set((parsed.extensions??[]).map(path=>resolve(cwd,path)))].sort(),multi_session:parsed.multiSession===!0,session_runtime:resolveSessionRuntime(parsed)},canonical=JSON.stringify({extensions:core.extensions,multi_session:core.multi_session,session_runtime:core.session_runtime});return{profile_id:createHash("sha256").update(canonical).digest("hex"),core}}function socketHostLaunchProfile(hostArgs,cwd){return hostLaunchProfile(["--mode","rpc","--multi-session","--listen","unix://",...hostArgs],cwd)}function protocolIdentity(){cachedProfile??=hostLaunchProfile(process.argv.slice(2),process.cwd());let build=engineBuildIdentity();return{instanceId:INSTANCE_ID,generation:hostGeneration(process.env),engineVersion:build.text,engineOrdinal:build.ordinal,launch_profile:cachedProfile}}export{CUSTOM_UNSUPPORTED_CAPABILITY,EXTENSION_EVENTS_CAPABILITY,AUTO_TITLE_SESSIONS_CAPABILITY,CONTINUE_FROM_LEAF_CAPABILITY,MEDIA_PLACEHOLDERS_CAPABILITY,QUESTION_CAPABILITY,RETAIN_ON_DISCONNECT_CAPABILITY,SESSION_CONTEXT_CAPABILITY,SESSION_KIND_CAPABILITY,AUTO_TITLE_PER_SESSION_CAPABILITY,WARM_CAPABILITY,DURABLE_SESSION_ID_CAPABILITY,PROMPT_SURFACE_CAPABILITY,BROWSER_ENGINE_CAPABILITY,RETRY_FALLBACK_PROFILE_CAPABILITY,PROMPT_SURFACE_CHAT_CAPABILITY,RPC_CLIENT_CAPABILITIES_ENV,parseClientCapabilities,DEFAULT_CUSTOM_EXTENSION_LABEL,buildCustomUnsupportedRequest,hostInstanceId,hostGeneration,hostLaunchProfile,socketHostLaunchProfile,protocolIdentity};
|