@deepseek-ai/dsh-api-session-controller 0.2.1-alpha.1 → 0.2.1-alpha.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/README.i18n.yaml +2 -2
- package/README.md +3 -1
- package/README.zh.md +3 -1
- package/lib/client.js +44 -20
- package/lib/index.js +43 -13
- package/lib/typert.host.js +227 -41
- package/lib/typert.remote-client.js +199 -21
- package/lib/types/client/contract/sessions.d.ts +4 -2
- package/lib/types/client/sessions/lineage.d.ts +2 -0
- package/lib/types/client/sessions/manager.d.ts +7 -5
- package/lib/types/client/sessions/manager.js +36 -11
- package/lib/types/client/sessions/projection-store.d.ts +3 -3
- package/lib/types/client/sessions/projection-store.js +1 -1
- package/lib/types/client/sessions/service.d.ts +8 -4
- package/lib/types/client/sessions/service.js +7 -3
- package/lib/types/index.d.ts +5 -4
- package/lib/types/index.js +40 -10
- package/lib/types/list.js +6 -4
- package/lib/types/skill-catalog.js +7 -6
- package/lib/types/types.d.ts +14 -2
- package/package.json +78 -75
package/README.i18n.yaml
CHANGED
|
@@ -15,8 +15,8 @@
|
|
|
15
15
|
en: 1fdf707322fb6996
|
|
16
16
|
zh: 0204f7e9d4aa3cce
|
|
17
17
|
/session-controller/use-this-package:
|
|
18
|
-
en:
|
|
19
|
-
zh:
|
|
18
|
+
en: d26a58a8f2406cd5
|
|
19
|
+
zh: 82135eceb9adb628
|
|
20
20
|
/session-controller/client-references:
|
|
21
21
|
en: 41bdda1ac32df558
|
|
22
22
|
zh: aa5d0e325924066b
|
package/README.md
CHANGED
|
@@ -31,7 +31,9 @@ History pages and follow opening snapshots carry one `{ type: 'event', event: Se
|
|
|
31
31
|
|
|
32
32
|
The Client journal validates current Session event envelopes before publishing follow snapshots, live entries, or history pages. It reuses the browser-safe Session validators for required surface markers, exact replacement endpoints, earlier unique source seqs, embedded Assistant provider metadata, request-header omissions, and tool-error consistency. Invalid records fail without field stripping or normalization; range membership and source existence remain durable-log checks on the Host.
|
|
33
33
|
|
|
34
|
-
Each endpoint states its activation policy. List reads only stored headers and projection-cache rows: it never calls per-session stat or opens a cold Session body. A current-format cache identity may supply every list hint; a lifecycle-matching predecessor cache may supply only its version-compatible title as a stale display fact, never as an authoritative fold seed. Search, attachment, history pages, log following, skill discovery, and workspace-path opening can inspect persistence without activating an Agent; `canOpenWorkspacePath()` reports native-opening availability without addressing a Session. Cancellation requires live state; queue mutation, model, rename, prompt, and file-reference operations may resolve or resume an ordinary Session. Prompt rejects content with neither non-whitespace text nor an attachment before resolving the Agent or appending Session events; queue edits accept only non-empty text content. Prompt admission consumes opaque receipts from the injected [`fileUploads`](../../client/file-upload/README.md) Host service and resolves every same-Agent receipt before sending the complete ordered content list through `ctx.attachments`. Prompt retries whose `requestId` is already queued or logged return the original acceptance without inserting another message. Create and fork are the only operations that create a new Agent directly. The service applies one preset-aware resume policy and subagent ownership fence to its own methods and to the Typert Agent and Session lookups used by other Remote namespaces. Queue mutation has one narrow exception: a live child whose current projected identity is continuable and comes from its own non-seed suffix accepts the ordinary Edit, Remove, and QueueDock Steer actions across both inbox destinations. One-shot, missing, unknown, corrupt, seed-only, or cold children remain rejected without resume. The skill catalog uses a live Agent when present
|
|
34
|
+
Each endpoint states its activation policy. List reads only stored headers and projection-cache rows: it never calls per-session stat or opens a cold Session body. A current-format cache identity may supply every list hint; a lifecycle-matching predecessor cache may supply only its version-compatible title as a stale display fact, never as an authoritative fold seed. Search, attachment, history pages, log following, skill discovery, and workspace-path opening can inspect persistence without activating an Agent; `canOpenWorkspacePath()` reports native-opening availability without addressing a Session. Cancellation requires live state; queue mutation, model, rename, prompt, and file-reference operations may resolve or resume an ordinary Session. Prompt rejects content with neither non-whitespace text nor an attachment before resolving the Agent or appending Session events; queue edits accept only non-empty text content. Prompt admission consumes opaque receipts from the injected [`fileUploads`](../../client/file-upload/README.md) Host service and resolves every same-Agent receipt before sending the complete ordered content list through `ctx.attachments`. Prompt retries whose `requestId` is already queued or logged return the original acceptance without inserting another message. Create and fork are the only operations that create a new Agent directly. The service applies one preset-aware resume policy and subagent ownership fence to its own methods and to the Typert Agent and Session lookups used by other Remote namespaces. Queue mutation has one narrow exception: a live child whose current projected identity is continuable and comes from its own non-seed suffix accepts the ordinary Edit, Remove, and QueueDock Steer actions across both inbox destinations. One-shot, missing, unknown, corrupt, seed-only, or cold children remain rejected without resume. The skill catalog uses a live Agent's effective working directory and scoped registry when present. Cold listing uses the projected current directory, falling back to the recorded origin and then the configured directory default. It retains the preset standing scope without activating an Agent. The authenticated delivery routes use `workspaceDesktop()` for the serving Host name and file-manager behavior. `openWorkspacePath({ path, action: "reveal" })` delegates file-manager navigation to the native adapter; omitting `action` opens the file-type association, including HTML and SVG. Both operations require the composed filesystem to map the requested Host path to the same canonical process path; unmapped remote paths are refused before a native command runs. `session.projections` normally reads an exact baseline, including current-format cold logs, and returns `kind: sequenced`. Only a read requiring migration returns `kind: migration-required` hints without `asOfSeq`; missing keys remain absent. It returns null for a missing Session. Fork callers may set `allowMigration: false` to receive `session/migration-required` instead of starting source migration; omission retains automatic migration. The Client exposes shared values and explicit-read state through `projectionsBySession`; domains select their own keys. Session-list summaries carry `agentAvailable`, updated through existing summary and status events independently of durable projections. Sequenced reads and live projection frames use the same sequence ordering; cached reads fill hints without replacing sequenced values.
|
|
35
|
+
|
|
36
|
+
List rows carry `formatStatus` when available, so Clients can identify migration-required Sessions without requesting their projections.
|
|
35
37
|
|
|
36
38
|
Host list generation yields between complete rows only when its configured work slice is exhausted; cheap remaining work returns without a forced yield. Request cancellation is checked per row and after each yield and rejects without partial results. Each row is synchronous; the list does not promise one cross-Session snapshot. A Session already queued as cold remains a cached row if it attaches during a yield: Client in-flight mutation replay corrects its availability, and sequenced projections supersede cached hints.
|
|
37
39
|
|
package/README.zh.md
CHANGED
|
@@ -31,7 +31,9 @@ kind: "package-reference"
|
|
|
31
31
|
|
|
32
32
|
Client journal 在发布 follow 快照、live entry 或历史页之前验证当前 Session 事件 envelope。它复用浏览器安全的 Session validator,检查必需的 surface marker、精确的 replacement endpoint、更早且唯一的 source seq、内嵌 Assistant 提供方元数据、request header 可选字段的省略规则以及工具错误一致性。无效 record 直接失败,不删除字段或归一化;范围成员与来源存在性仍由 Host 的持久日志检查。
|
|
33
33
|
|
|
34
|
-
每个 endpoint 都声明自己的激活策略。列表只读取持久化 header 与 projection cache row,绝不调用逐 Session stat 或打开冷 Session body。当前格式 cache identity 可以提供全部列表 hint;生命周期匹配的 predecessor cache 只能提供版本兼容的 title,作为可能过时的展示事实,绝不能作为权威 fold seed。搜索、附件、历史页、日志跟随、skill 发现和工作区路径打开可以在不激活 Agent 的情况下检查 persistence;`canOpenWorkspacePath()` 无需指定 Session 即可报告原生打开能力。取消要求 live 状态;queue 变更、模型、重命名、prompt 和文件引用操作可以解析或恢复普通 Session。提示词会在解析 Agent 或追加 Session 事件前,拒绝既没有非空白文本也没有附件的 content;queue edit 只接受非空文本 content。prompt 准入从注入的 [`fileUploads`](../../client/file-upload/README.zh.md) Host 服务取得不透明凭证,在把完整有序内容列表交给 `ctx.attachments` 前解析每个属于同一 Agent 的凭证。`requestId` 已进入 queue 或日志时,prompt 重试直接返回原来的接受结果,不会重复插入消息。只有 create 与 fork 会直接创建新 Agent。该服务把同一套感知 preset 的恢复策略和 subagent ownership fence 同时用于自身方法,以及其他 Remote namespace 使用的 Typert Agent 与 Session lookup。Queue 变更只有一个狭窄例外:当前 projection identity 为 continuable 且来自自身非 seed suffix 的在线 child,可以在两个 inbox 目标上使用普通 Edit、Remove 与 QueueDock Steer action。One-shot、缺失、未知、损坏、仅含 seed identity 或冷 child 继续被拒绝,且不会恢复。skill 目录优先使用已有 live Agent
|
|
34
|
+
每个 endpoint 都声明自己的激活策略。列表只读取持久化 header 与 projection cache row,绝不调用逐 Session stat 或打开冷 Session body。当前格式 cache identity 可以提供全部列表 hint;生命周期匹配的 predecessor cache 只能提供版本兼容的 title,作为可能过时的展示事实,绝不能作为权威 fold seed。搜索、附件、历史页、日志跟随、skill 发现和工作区路径打开可以在不激活 Agent 的情况下检查 persistence;`canOpenWorkspacePath()` 无需指定 Session 即可报告原生打开能力。取消要求 live 状态;queue 变更、模型、重命名、prompt 和文件引用操作可以解析或恢复普通 Session。提示词会在解析 Agent 或追加 Session 事件前,拒绝既没有非空白文本也没有附件的 content;queue edit 只接受非空文本 content。prompt 准入从注入的 [`fileUploads`](../../client/file-upload/README.zh.md) Host 服务取得不透明凭证,在把完整有序内容列表交给 `ctx.attachments` 前解析每个属于同一 Agent 的凭证。`requestId` 已进入 queue 或日志时,prompt 重试直接返回原来的接受结果,不会重复插入消息。只有 create 与 fork 会直接创建新 Agent。该服务把同一套感知 preset 的恢复策略和 subagent ownership fence 同时用于自身方法,以及其他 Remote namespace 使用的 Typert Agent 与 Session lookup。Queue 变更只有一个狭窄例外:当前 projection identity 为 continuable 且来自自身非 seed suffix 的在线 child,可以在两个 inbox 目标上使用普通 Edit、Remove 与 QueueDock Steer action。One-shot、缺失、未知、损坏、仅含 seed identity 或冷 child 继续被拒绝,且不会恢复。skill 目录优先使用已有 live Agent 的有效工作目录与限定范围内的注册表。冷态列表使用投影中的当前目录,依次回退到记录的起始目录与配置的默认目录。它保留 preset 常驻 scope,不会激活 Agent。经过鉴权的文件交付路由通过 `workspaceDesktop()` 获取提供服务的 Host 名称和文件管理器行为。`openWorkspacePath({ path, action: "reveal" })` 将文件管理器导航委托给原生适配器;省略 `action` 时按文件类型关联打开,包括 HTML 和 SVG。两种操作都要求当前文件系统将请求的 Host 路径映射到同一个规范进程路径;无法映射的远端路径会在执行原生命令之前被拒绝。 `session.projections` 正常读取准确基线,包括当前格式的冷日志,返回 `kind: sequenced`。只有需要迁移时才返回不带 `asOfSeq` 的 `kind: migration-required` 提示,缺少的键保持缺席。Session 不存在时返回 null。Fork 调用者可设置 `allowMigration: false`,在源历史需要迁移时收到 `session/migration-required`;省略则保持自动迁移。Client 通过 `projectionsBySession` 暴露共享值和显式读取状态,由领域选择自身的 key。Session 列表摘要携带 `agentAvailable`,通过已有摘要与状态事件更新,与持久化 projection 相互独立。有序读取与实时 projection 帧使用相同的序号排序规则;缓存读取只补充提示,不替换有序值。
|
|
35
|
+
|
|
36
|
+
列表行在状态可用时携带 `formatStatus`,Client 无需请求其投影即可识别需要迁移的 Session。
|
|
35
37
|
|
|
36
38
|
Host 仅在配置的工作时间片耗尽时,于完整条目之间让出事件循环;剩余工作较少时直接返回,不强制让出。每行及每次让出之后检查请求取消,取消时拒绝返回部分结果。每行同步计算;列表不承诺跨 Session 的统一快照。已经进入冷态队列的 Session 若在让出期间接入,仍返回缓存行:Client 对进行中请求的变更重放会纠正其可用性,带序号的投影会覆盖缓存提示。
|
|
37
39
|
|
package/lib/client.js
CHANGED
|
@@ -193,6 +193,7 @@ window.__ModuleLoader__.load({
|
|
|
193
193
|
"turn/start",
|
|
194
194
|
"user/message",
|
|
195
195
|
"web/deepseek-search-llm-request",
|
|
196
|
+
"working-directory/change",
|
|
196
197
|
"workspace/changes"
|
|
197
198
|
]);
|
|
198
199
|
//#endregion
|
|
@@ -1616,7 +1617,7 @@ window.__ModuleLoader__.load({
|
|
|
1616
1617
|
this.changed(key);
|
|
1617
1618
|
}
|
|
1618
1619
|
/**
|
|
1619
|
-
* Fill keys from a
|
|
1620
|
+
* Fill keys from a Host block labeled `cached`: a zero-I/O
|
|
1620
1621
|
* view of the persisted checkpoint. A cached value lands only where no
|
|
1621
1622
|
* sequenced row exists: a connected Session has already answered for such
|
|
1622
1623
|
* a key, and the list's view of the persisted checkpoint cannot be newer
|
|
@@ -3011,20 +3012,28 @@ window.__ModuleLoader__.load({
|
|
|
3011
3012
|
}
|
|
3012
3013
|
/**
|
|
3013
3014
|
* Resolve an address for breadcrumb navigation without retaining transport authority.
|
|
3014
|
-
* @param sessionId - possible child id in
|
|
3015
|
-
* @returns A retained
|
|
3015
|
+
* @param sessionId - possible child id in received Session metadata.
|
|
3016
|
+
* @returns A retained address, then a catalog address, then an unknown-mode address
|
|
3017
|
+
* from a listed subagent header; undefined without that evidence.
|
|
3016
3018
|
*/
|
|
3017
3019
|
subagentAddress(sessionId) {
|
|
3018
3020
|
const retained = this.addresses.get(sessionId);
|
|
3019
3021
|
if (retained !== void 0) return retained;
|
|
3020
3022
|
for (const parentSessionId of this.projectionStores.keys()) {
|
|
3021
3023
|
const child = this.projectionStores.get(parentSessionId)?.values().subagentCatalog?.find((entry) => entry.id === sessionId);
|
|
3022
|
-
if (child !== void 0) return {
|
|
3024
|
+
if (child !== void 0 && child.mode !== "external") return {
|
|
3023
3025
|
parentSessionId,
|
|
3024
3026
|
childSessionId: sessionId,
|
|
3025
3027
|
mode: child.mode
|
|
3026
3028
|
};
|
|
3027
3029
|
}
|
|
3030
|
+
const summary = this.summaries.find((summary) => summary.sessionId === sessionId);
|
|
3031
|
+
if (summary?.origin !== "subagent" || summary.parentSessionId === void 0) return void 0;
|
|
3032
|
+
return {
|
|
3033
|
+
parentSessionId: summary.parentSessionId,
|
|
3034
|
+
childSessionId: sessionId,
|
|
3035
|
+
mode: "unknown"
|
|
3036
|
+
};
|
|
3028
3037
|
}
|
|
3029
3038
|
/**
|
|
3030
3039
|
* Withdraw an exact Client instance before running its teardown callbacks.
|
|
@@ -3090,7 +3099,8 @@ window.__ModuleLoader__.load({
|
|
|
3090
3099
|
session.handleRunning(summary.running);
|
|
3091
3100
|
} else {
|
|
3092
3101
|
const address = this.addresses.get(sessionId);
|
|
3093
|
-
|
|
3102
|
+
const child = address === void 0 ? void 0 : this.projectionStores.get(address.parentSessionId)?.values().subagentCatalog?.find((entry) => entry.id === sessionId);
|
|
3103
|
+
if (child !== void 0 && child.mode !== "external") {
|
|
3094
3104
|
session.handleBlank(false);
|
|
3095
3105
|
session.handleRunning(false);
|
|
3096
3106
|
} else session.handleBlank(true);
|
|
@@ -3158,7 +3168,7 @@ window.__ModuleLoader__.load({
|
|
|
3158
3168
|
return store;
|
|
3159
3169
|
}
|
|
3160
3170
|
/**
|
|
3161
|
-
*
|
|
3171
|
+
* Read projections without starting migration; retry failed or migration-deferred reads.
|
|
3162
3172
|
* @param sessionId - Session to inspect without opening its conversation.
|
|
3163
3173
|
* @returns completion of the current or newly started read.
|
|
3164
3174
|
*/
|
|
@@ -3179,13 +3189,22 @@ window.__ModuleLoader__.load({
|
|
|
3179
3189
|
const result = await this.remote.session.projections({ sessionId }, controller.signal);
|
|
3180
3190
|
if (controller.signal.aborted) return;
|
|
3181
3191
|
if (result.ok) {
|
|
3182
|
-
|
|
3183
|
-
|
|
3184
|
-
|
|
3185
|
-
|
|
3192
|
+
const value = result.value;
|
|
3193
|
+
if (value !== null) switch (value.kind) {
|
|
3194
|
+
case "sequenced":
|
|
3195
|
+
store.seed({
|
|
3196
|
+
values: value.values,
|
|
3197
|
+
asOfSeq: sessionSeqCursor(value.asOfSeq)
|
|
3198
|
+
});
|
|
3199
|
+
break;
|
|
3200
|
+
case "migration-required":
|
|
3201
|
+
store.applyCached(value.values);
|
|
3202
|
+
break;
|
|
3203
|
+
default: assertNever(value, "session projections result");
|
|
3204
|
+
}
|
|
3186
3205
|
else if (store.values() === initialValues) store.clear();
|
|
3187
3206
|
this.projectionLoads.set(sessionId, {
|
|
3188
|
-
state: "ready",
|
|
3207
|
+
state: value?.kind === "migration-required" ? "migration-required" : "ready",
|
|
3189
3208
|
error: null
|
|
3190
3209
|
});
|
|
3191
3210
|
} else this.projectionLoads.set(sessionId, {
|
|
@@ -3340,14 +3359,15 @@ window.__ModuleLoader__.load({
|
|
|
3340
3359
|
* preserve it or lower it after an exact cut before the first `turn/start`;
|
|
3341
3360
|
* lineage rides parentSessionId. A child published before Workspace
|
|
3342
3361
|
* attachment fails is also reconciled into the list.
|
|
3343
|
-
* @param opts - source session
|
|
3362
|
+
* @param opts - source session, optional exact inclusive boundary seq, and permission to start migration.
|
|
3344
3363
|
* @returns the fork result (the child session id).
|
|
3345
3364
|
*/
|
|
3346
3365
|
async fork(opts) {
|
|
3347
3366
|
const source = this.summaries.find((s) => s.sessionId === opts.sessionId);
|
|
3348
3367
|
const result = await this.remote.session.fork({
|
|
3349
3368
|
sessionId: opts.sessionId,
|
|
3350
|
-
...opts.atSeq === void 0 ? {} : { atSeq: opts.atSeq }
|
|
3369
|
+
...opts.atSeq === void 0 ? {} : { atSeq: opts.atSeq },
|
|
3370
|
+
...opts.allowMigration === void 0 ? {} : { allowMigration: opts.allowMigration }
|
|
3351
3371
|
});
|
|
3352
3372
|
const childId = result.ok ? result.value.sessionId : workspaceAttachSessionId(result.error);
|
|
3353
3373
|
if (childId !== void 0) this.recordMutation({
|
|
@@ -3572,7 +3592,7 @@ window.__ModuleLoader__.load({
|
|
|
3572
3592
|
};
|
|
3573
3593
|
})).map((entry) => {
|
|
3574
3594
|
const prev = this.entryCache.get(entry.sessionId);
|
|
3575
|
-
if (prev !== void 0 && prev.updatedAt === entry.updatedAt && prev.running === entry.running && prev.blank === entry.blank && prev.parentSessionId === entry.parentSessionId && prev.cwd === entry.cwd && prev.origin === entry.origin && prev.title === entry.title && prev.depth === entry.depth && prev.projectionValues === entry.projectionValues) return prev;
|
|
3595
|
+
if (prev !== void 0 && prev.updatedAt === entry.updatedAt && prev.running === entry.running && prev.blank === entry.blank && prev.parentSessionId === entry.parentSessionId && prev.cwd === entry.cwd && prev.origin === entry.origin && prev.title === entry.title && prev.depth === entry.depth && prev.formatStatus === entry.formatStatus && prev.projectionValues === entry.projectionValues) return prev;
|
|
3576
3596
|
this.entryCache.set(entry.sessionId, entry);
|
|
3577
3597
|
return entry;
|
|
3578
3598
|
});
|
|
@@ -3605,13 +3625,14 @@ window.__ModuleLoader__.load({
|
|
|
3605
3625
|
blank: existing.blank && mutation.summary.blank,
|
|
3606
3626
|
...mutation.kind === "upsert" ? {
|
|
3607
3627
|
agentAvailable: mutation.summary.agentAvailable,
|
|
3608
|
-
running: mutation.summary.running
|
|
3628
|
+
running: mutation.summary.running,
|
|
3629
|
+
...mutation.summary.formatStatus === void 0 ? {} : { formatStatus: mutation.summary.formatStatus }
|
|
3609
3630
|
} : {},
|
|
3610
3631
|
...existing.cwd === void 0 && mutation.summary.cwd !== void 0 ? { cwd: mutation.summary.cwd } : {},
|
|
3611
3632
|
...existing.parentSessionId === void 0 && mutation.summary.parentSessionId !== void 0 ? { parentSessionId: mutation.summary.parentSessionId } : {},
|
|
3612
3633
|
...existing.origin === void 0 && mutation.summary.origin !== void 0 ? { origin: mutation.summary.origin } : {}
|
|
3613
3634
|
};
|
|
3614
|
-
if (filled.cwd === existing.cwd && filled.parentSessionId === existing.parentSessionId && filled.origin === existing.origin && filled.blank === existing.blank && filled.agentAvailable === existing.agentAvailable && filled.running === existing.running) return [...summaries];
|
|
3635
|
+
if (filled.cwd === existing.cwd && filled.parentSessionId === existing.parentSessionId && filled.origin === existing.origin && filled.blank === existing.blank && filled.agentAvailable === existing.agentAvailable && filled.running === existing.running && filled.formatStatus === existing.formatStatus) return [...summaries];
|
|
3615
3636
|
return summaries.map((summary) => summary.sessionId === mutation.summary.sessionId ? filled : summary);
|
|
3616
3637
|
}
|
|
3617
3638
|
case "remove": return summaries.filter((summary) => summary.sessionId !== mutation.sessionId);
|
|
@@ -3861,13 +3882,13 @@ window.__ModuleLoader__.load({
|
|
|
3861
3882
|
* Resolve an already discovered direct-parent address without opening it.
|
|
3862
3883
|
* Feature plugins use this to avoid Agent-bound RPCs in persisted child views.
|
|
3863
3884
|
* @param id - possible addressed child id.
|
|
3864
|
-
* @returns A retained or
|
|
3885
|
+
* @returns A retained, catalog-derived, or unknown-mode listed address, without retaining a new selection or scope.
|
|
3865
3886
|
*/
|
|
3866
3887
|
subagentAddress(id) {
|
|
3867
3888
|
return this.manager.subagentAddress(id);
|
|
3868
3889
|
}
|
|
3869
3890
|
/**
|
|
3870
|
-
*
|
|
3891
|
+
* Read projections without starting migration; retry failed or migration-deferred reads.
|
|
3871
3892
|
* @param sessionId - Session to inspect without opening its conversation.
|
|
3872
3893
|
*/
|
|
3873
3894
|
refreshProjections(sessionId) {
|
|
@@ -3957,7 +3978,7 @@ window.__ModuleLoader__.load({
|
|
|
3957
3978
|
* seq (a real event seq the caller already knows; a cut inside an open
|
|
3958
3979
|
* turn is balanced Host-side with synthetic closers, and omission selects
|
|
3959
3980
|
* the latest completed-turn prefix), and whether to increment an
|
|
3960
|
-
* inherited durable title before resolving.
|
|
3981
|
+
* inherited durable title before resolving, plus permission to start source migration.
|
|
3961
3982
|
* @returns the child session id.
|
|
3962
3983
|
* @throws {SessionForkError} with the source id.
|
|
3963
3984
|
* @throws {Error} when a requested child-title rename fails after creation.
|
|
@@ -3966,7 +3987,8 @@ window.__ModuleLoader__.load({
|
|
|
3966
3987
|
const sourceTitle = opts.increaseTitle ? this.list.getSnapshot().byId[opts.sessionId]?.title : void 0;
|
|
3967
3988
|
const result = await this.manager.fork({
|
|
3968
3989
|
sessionId: opts.sessionId,
|
|
3969
|
-
...opts.atSeq === void 0 ? {} : { atSeq: SessionSeq(opts.atSeq) }
|
|
3990
|
+
...opts.atSeq === void 0 ? {} : { atSeq: SessionSeq(opts.atSeq) },
|
|
3991
|
+
...opts.allowMigration === void 0 ? {} : { allowMigration: opts.allowMigration }
|
|
3970
3992
|
});
|
|
3971
3993
|
if (!result.ok) throw new SessionForkError(result.error, opts.sessionId);
|
|
3972
3994
|
this.projectList();
|
|
@@ -4130,6 +4152,7 @@ window.__ModuleLoader__.load({
|
|
|
4130
4152
|
retainedBy: this.retentionSnapshot(entry.sessionId).retainedBy,
|
|
4131
4153
|
blank: entry.blank,
|
|
4132
4154
|
updatedAt: entry.updatedAt,
|
|
4155
|
+
formatStatus: entry.formatStatus,
|
|
4133
4156
|
...entry.projectionValues === void 0 ? {} : { projectionValues: entry.projectionValues },
|
|
4134
4157
|
...entry.title !== void 0 ? { title: entry.title } : {},
|
|
4135
4158
|
...entry.cwd !== void 0 ? { cwd: entry.cwd } : {},
|
|
@@ -4138,6 +4161,7 @@ window.__ModuleLoader__.load({
|
|
|
4138
4161
|
};
|
|
4139
4162
|
}
|
|
4140
4163
|
for (const [parentId, projection] of Object.entries(projectionsBySession)) for (const child of projection.values.subagentCatalog ?? []) {
|
|
4164
|
+
if (child.mode === "external") continue;
|
|
4141
4165
|
const childId = child.id;
|
|
4142
4166
|
const summary = byId[childId];
|
|
4143
4167
|
const projectionValues = summary?.projectionValues ?? this.manager.projectionValues(childId);
|
package/lib/index.js
CHANGED
|
@@ -1881,6 +1881,7 @@ var ApiSessionList = class {
|
|
|
1881
1881
|
sessionId: session.id,
|
|
1882
1882
|
updatedAt: updatedAt(session.header, metadata),
|
|
1883
1883
|
agentAvailable: this.ctx.agents.get(session.id)?.session === session,
|
|
1884
|
+
formatStatus: "current",
|
|
1884
1885
|
running: this.ctx.agents.get(session.id)?.status === "running",
|
|
1885
1886
|
blank: metadata?.blank ?? session.seq === 0,
|
|
1886
1887
|
...listFields(session.header),
|
|
@@ -1903,16 +1904,16 @@ var ApiSessionList = class {
|
|
|
1903
1904
|
signal?.throwIfAborted();
|
|
1904
1905
|
const live = this.ctx.sessions.get(record.header.id);
|
|
1905
1906
|
if (live !== void 0) items.push(this.summaryFor(live));
|
|
1906
|
-
else if (record.header.cwd !== void 0) cold.push(record
|
|
1907
|
+
else if (record.header.cwd !== void 0) cold.push(record);
|
|
1907
1908
|
if (performance.now() >= yieldDeadline) {
|
|
1908
1909
|
await scheduler.yield();
|
|
1909
1910
|
signal?.throwIfAborted();
|
|
1910
1911
|
yieldDeadline = performance.now() + this.workSliceMs;
|
|
1911
1912
|
}
|
|
1912
1913
|
}
|
|
1913
|
-
for (const
|
|
1914
|
+
for (const record of cold) {
|
|
1914
1915
|
signal?.throwIfAborted();
|
|
1915
|
-
items.push(this.summarizeCold(
|
|
1916
|
+
items.push(this.summarizeCold(record));
|
|
1916
1917
|
if (performance.now() >= yieldDeadline) {
|
|
1917
1918
|
await scheduler.yield();
|
|
1918
1919
|
signal?.throwIfAborted();
|
|
@@ -1923,13 +1924,14 @@ var ApiSessionList = class {
|
|
|
1923
1924
|
items.sort((left, right) => right.updatedAt - left.updatedAt);
|
|
1924
1925
|
return items;
|
|
1925
1926
|
}
|
|
1926
|
-
summarizeCold(header) {
|
|
1927
|
+
summarizeCold({ header, formatStatus }) {
|
|
1927
1928
|
const projections = this.projectionsFor(header, void 0);
|
|
1928
1929
|
const metadata = projections?.values.sessionListMetadata;
|
|
1929
1930
|
return {
|
|
1930
1931
|
sessionId: header.id,
|
|
1931
1932
|
updatedAt: updatedAt(header, metadata),
|
|
1932
1933
|
agentAvailable: false,
|
|
1934
|
+
...formatStatus === void 0 ? {} : { formatStatus },
|
|
1933
1935
|
running: false,
|
|
1934
1936
|
blank: metadata?.blank ?? false,
|
|
1935
1937
|
...listFields(header),
|
|
@@ -2261,7 +2263,8 @@ let SessionSkillCatalog = (() => {
|
|
|
2261
2263
|
static inject = [
|
|
2262
2264
|
"agents",
|
|
2263
2265
|
"sessionQuery",
|
|
2264
|
-
"typert"
|
|
2266
|
+
"typert",
|
|
2267
|
+
"workingDirectory"
|
|
2265
2268
|
];
|
|
2266
2269
|
/** @param ctx - Host context carrying Session reads and optional skill/preset services. */
|
|
2267
2270
|
constructor(ctx) {
|
|
@@ -2294,8 +2297,10 @@ let SessionSkillCatalog = (() => {
|
|
|
2294
2297
|
try {
|
|
2295
2298
|
const observation = __addDisposableResource$1(env_2, await this.ctx.sessionQuery.observeSession(sessionId), false);
|
|
2296
2299
|
if (observation.projections === void 0) throw new Error("skill catalog requires a projected Session observation");
|
|
2297
|
-
cwd = observation.header.cwd;
|
|
2300
|
+
cwd = observation.projections.values.workingDirectory ?? observation.header.cwd ?? this.ctx.workingDirectory.defaultDirectory;
|
|
2298
2301
|
agentPreset = observation.projections.values.agentPreset ?? void 0;
|
|
2302
|
+
const live = this.ctx.agents.get(sessionId);
|
|
2303
|
+
if (live !== void 0) cwd = await this.ctx.workingDirectory.ensure(live, signal);
|
|
2299
2304
|
} catch (e_1) {
|
|
2300
2305
|
env_2.error = e_1;
|
|
2301
2306
|
env_2.hasError = true;
|
|
@@ -2306,7 +2311,6 @@ let SessionSkillCatalog = (() => {
|
|
|
2306
2311
|
if (error instanceof SessionQueryError && error.code === "SESSION_QUERY_SESSION_NOT_FOUND") throw new RemoteError("session/not-found", `session "${sessionId}" not found`, { sessionId });
|
|
2307
2312
|
throw new RemoteError("gateway/internal", `session "${sessionId}" could not be inspected: ${String(error)}`, {});
|
|
2308
2313
|
}
|
|
2309
|
-
if (cwd === void 0) throw new RemoteError("gateway/internal", `session "${sessionId}" has no project cwd`, {});
|
|
2310
2314
|
const live = this.ctx.agents.get(sessionId);
|
|
2311
2315
|
const presets = this.ctx.get("agentPresets");
|
|
2312
2316
|
const skillRegistry = (live === void 0 ? void 0 : presets?.serviceFor(live, "skills")) ?? this.ctx.get("skills");
|
|
@@ -3104,10 +3108,13 @@ let SessionController = (() => {
|
|
|
3104
3108
|
* Fork one cold-readable exact event prefix into a new Session. An omitted
|
|
3105
3109
|
* boundary selects the latest completed-turn prefix; an open cut receives
|
|
3106
3110
|
* synthetic fork closers.
|
|
3107
|
-
* @param request - source Session
|
|
3111
|
+
* @param request - source Session, optional exact inclusive event boundary, and migration preflight choice.
|
|
3108
3112
|
* @returns the new Session identity.
|
|
3109
3113
|
*/
|
|
3110
|
-
fork(request) {
|
|
3114
|
+
async fork(request) {
|
|
3115
|
+
if (request.allowMigration === false && this.ctx.sessions.get(request.sessionId) === void 0) {
|
|
3116
|
+
if ((await this.ctx.get("sessionPersistence")?.stat(request.sessionId))?.formatStatus === "migration-required" && this.ctx.sessions.get(request.sessionId) === void 0) throw new RemoteError("session/migration-required", "Open the source Session to complete migration before forking", { sessionId: request.sessionId });
|
|
3117
|
+
}
|
|
3111
3118
|
return this.commands.fork(request);
|
|
3112
3119
|
}
|
|
3113
3120
|
/**
|
|
@@ -3164,10 +3171,10 @@ let SessionController = (() => {
|
|
|
3164
3171
|
return this.history.follow(request, signal);
|
|
3165
3172
|
}
|
|
3166
3173
|
/**
|
|
3167
|
-
* Read
|
|
3174
|
+
* Read exact projections, returning cached hints only when migration is required.
|
|
3168
3175
|
* @param request - Session whose current values are required.
|
|
3169
|
-
* @param signal - cancellation for the Session
|
|
3170
|
-
* @returns
|
|
3176
|
+
* @param signal - cancellation for the Session read.
|
|
3177
|
+
* @returns a sequenced baseline, cached hints when migration is deferred, or null when absent.
|
|
3171
3178
|
*/
|
|
3172
3179
|
async projections(request, signal) {
|
|
3173
3180
|
const { sessionId } = request;
|
|
@@ -3179,9 +3186,22 @@ let SessionController = (() => {
|
|
|
3179
3186
|
hasError: false
|
|
3180
3187
|
};
|
|
3181
3188
|
try {
|
|
3189
|
+
signal.throwIfAborted();
|
|
3190
|
+
const live = this.liveProjections(sessionId);
|
|
3191
|
+
if (live !== void 0) return live;
|
|
3192
|
+
const stored = await this.ctx.get("sessionPersistence")?.stat(sessionId, { signal });
|
|
3193
|
+
signal.throwIfAborted();
|
|
3194
|
+
const attached = this.liveProjections(sessionId);
|
|
3195
|
+
if (attached !== void 0) return attached;
|
|
3196
|
+
if (stored === void 0) return null;
|
|
3197
|
+
if (stored.formatStatus === "migration-required") return {
|
|
3198
|
+
kind: "migration-required",
|
|
3199
|
+
values: { ...(this.ctx.get("sessionProjectionCache")?.cachedSnapshot(stored.header))?.values }
|
|
3200
|
+
};
|
|
3182
3201
|
const projections = __addDisposableResource(env_2, await this.ctx.sessionQuery.observeSession(sessionId, { signal }), false).projections;
|
|
3183
3202
|
if (projections === void 0) throw new RemoteError("session/projections-unavailable", "Session projections are unavailable", {});
|
|
3184
3203
|
return {
|
|
3204
|
+
kind: "sequenced",
|
|
3185
3205
|
asOfSeq: projections.asOfSeq,
|
|
3186
3206
|
values: projections.values
|
|
3187
3207
|
};
|
|
@@ -3192,12 +3212,22 @@ let SessionController = (() => {
|
|
|
3192
3212
|
__disposeResources(env_2);
|
|
3193
3213
|
}
|
|
3194
3214
|
} catch (error) {
|
|
3195
|
-
if (error instanceof SessionQueryError && error.code === "SESSION_QUERY_SESSION_NOT_FOUND") return null;
|
|
3215
|
+
if (error instanceof SessionQueryError && error.code === "SESSION_QUERY_SESSION_NOT_FOUND") return this.liveProjections(sessionId) ?? null;
|
|
3196
3216
|
if (signal.aborted || error instanceof SessionQueryError && error.code === "SESSION_QUERY_ABORTED") throw new RemoteError("gateway/cancelled", "Session projection read was cancelled", {}, { cause: error });
|
|
3197
3217
|
if (error instanceof RemoteError) throw error;
|
|
3198
3218
|
throw new RemoteError("gateway/internal", "Session projection read failed", {}, { cause: error });
|
|
3199
3219
|
}
|
|
3200
3220
|
}
|
|
3221
|
+
liveProjections(sessionId) {
|
|
3222
|
+
const session = this.ctx.sessions.get(sessionId);
|
|
3223
|
+
if (session === void 0) return void 0;
|
|
3224
|
+
const snapshot = this.ctx.sessionProjections.snapshot(session);
|
|
3225
|
+
return {
|
|
3226
|
+
kind: "sequenced",
|
|
3227
|
+
asOfSeq: snapshot.asOfSeq,
|
|
3228
|
+
values: snapshot.values
|
|
3229
|
+
};
|
|
3230
|
+
}
|
|
3201
3231
|
/**
|
|
3202
3232
|
* Stream a complete live-control baseline followed by replacement frames.
|
|
3203
3233
|
* @param signal - cancellation owned by the Remote stream carrier.
|