maka-agent 0.2.0-dev.26.20260909 → 0.2.0-dev.27.20260910
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/dist/cli-core.js +21 -0
- package/dist/runtime-host-cli.js +43 -3
- package/dist/runtime-host-handoff-surface.js +10 -8
- package/dist/runtime-host-lifecycle-transaction.js +28 -3
- package/dist/runtime-host-local-source-retirement.js +1 -1
- package/dist/runtime-host-setup-command.js +29 -6
- package/dist/runtime-host-update-command.js +49 -5
- package/dist/session-export-command.js +72 -0
- package/native/runtime-host-windows-task-launcher/prebuilds/win32-x64/maka-runtime-host-task-launcher.exe +0 -0
- package/node_modules/@maka/core/dist/usage-stats/types.js +2 -0
- package/node_modules/@maka/core/dist/workhub-routing.js +126 -0
- package/node_modules/@maka/core/package.json +2 -1
- package/node_modules/@maka/runtime/dist/ai-sdk-turn.js +7 -3
- package/node_modules/@maka/runtime/dist/builtin-tools.js +21 -2
- package/node_modules/@maka/runtime/dist/plugin-agent-service.js +131 -0
- package/node_modules/@maka/runtime/dist/plugin-approval-service.js +33 -0
- package/node_modules/@maka/runtime/dist/plugin-attachment-service.js +53 -0
- package/node_modules/@maka/runtime/dist/plugin-command-service.js +100 -0
- package/node_modules/@maka/runtime/dist/plugin-data-services.js +332 -0
- package/node_modules/@maka/runtime/dist/plugin-fs-service.js +62 -0
- package/node_modules/@maka/runtime/dist/plugin-goal-service.js +61 -0
- package/node_modules/@maka/runtime/dist/plugin-invocation-signal.js +24 -0
- package/node_modules/@maka/runtime/dist/plugin-llm-service.js +85 -0
- package/node_modules/@maka/runtime/dist/plugin-lsp-service.js +94 -0
- package/node_modules/@maka/runtime/dist/plugin-scope-registry.js +91 -0
- package/node_modules/@maka/runtime/dist/plugin-session-query-service.js +71 -0
- package/node_modules/@maka/runtime/dist/plugin-shell-env-service.js +87 -0
- package/node_modules/@maka/runtime/dist/plugin-shell-service.js +74 -0
- package/node_modules/@maka/runtime/dist/plugin-skill-service.js +93 -0
- package/node_modules/@maka/runtime/dist/plugin-system-prompt-service.js +346 -0
- package/node_modules/@maka/runtime/dist/plugin-tool-service.js +18 -52
- package/node_modules/@maka/runtime/dist/plugin-user-question-service.js +43 -0
- package/node_modules/@maka/runtime/dist/plugin-web-service.js +69 -0
- package/node_modules/@maka/runtime/dist/session-export.js +177 -0
- package/node_modules/@maka/runtime/package.json +18 -0
- package/node_modules/@maka/runtime-host/dist/client/host-handoff-copy.js +67 -18
- package/node_modules/@maka/runtime-host/dist/client/host-handoff.js +46 -12
- package/node_modules/@maka/runtime-host/dist/operator/setup-frame.js +31 -21
- package/node_modules/@maka/runtime-host/dist/protocol/index.js +9 -1
- package/node_modules/@maka/runtime-host/dist/protocol/plugin-platform.js +30 -5
- package/node_modules/@maka/runtime-host/dist/protocol/workhub-coordination.js +25 -23
- package/node_modules/@maka/runtime-host/dist/server/client-capability-coordinator.js +62 -4
- package/node_modules/@maka/runtime-host/dist/server/execution-composition.js +452 -7
- package/node_modules/@maka/runtime-host/dist/server/execution-model-authority.js +74 -0
- package/node_modules/@maka/runtime-host/dist/server/hosted-execution-recovery.js +2 -2
- package/node_modules/@maka/runtime-host/dist/server/hosted-execution-tool-profile.js +30 -0
- package/node_modules/@maka/runtime-host/dist/server/interactive-run-composer.js +79 -16
- package/node_modules/@maka/runtime-host/dist/server/message-coordinator.js +25 -4
- package/node_modules/@maka/runtime-host/dist/server/plugin-data-runtime.js +234 -0
- package/node_modules/@maka/runtime-host/dist/server/plugin-platform-coordinator.js +6 -0
- package/node_modules/@maka/runtime-host/dist/server/plugin-platform.js +12 -0
- package/node_modules/@maka/runtime-host/dist/server/root-turn-coordinator.js +189 -92
- package/node_modules/@maka/runtime-host/dist/server/workhub-coordination-action-gate.js +44 -35
- package/node_modules/@maka/runtime-host/dist/server/workhub-coordination-coordinator.js +92 -10
- package/node_modules/@maka/storage/dist/agent-run-store.js +54 -10
- package/node_modules/@maka/storage/dist/context-offload-snapshot.js +13 -6
- package/node_modules/@maka/storage/dist/execution-stores.js +1 -1
- package/node_modules/@maka/storage/dist/session-bundle-policy.js +333 -30
- package/node_modules/@maka/storage/dist/session-store.js +2 -2
- package/node_modules/@maka/storage/dist/sqlite-session-metadata-store.js +12 -5
- package/node_modules/@maka/storage/package.json +2 -0
- package/package.json +1 -1
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Licensed to the Apache Software Foundation (ASF) under one
|
|
3
|
+
* or more contributor license agreements. See the NOTICE file
|
|
4
|
+
* distributed with this work for additional information
|
|
5
|
+
* regarding copyright ownership. The ASF licenses this file
|
|
6
|
+
* to you under the Apache License, Version 2.0 (the
|
|
7
|
+
* "License"); you may not use this file except in compliance
|
|
8
|
+
* with the License. You may obtain a copy of the License at
|
|
9
|
+
*
|
|
10
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
+
*
|
|
12
|
+
* Unless required by applicable law or agreed to in writing,
|
|
13
|
+
* software distributed under the License is distributed on an
|
|
14
|
+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
15
|
+
* KIND, either express or implied. See the License for the
|
|
16
|
+
* specific language governing permissions and limitations
|
|
17
|
+
* under the License.
|
|
18
|
+
*/
|
|
19
|
+
/**
|
|
20
|
+
* Export one Session, and the subagent subtree under it, as a portable bundle.
|
|
21
|
+
*
|
|
22
|
+
* The preparation is `exportSessionBundleState()` in `@maka/storage`, which
|
|
23
|
+
* already takes a consistent database snapshot, keeps writers out, filters the
|
|
24
|
+
* copy down to the exported Sessions, carries the context-offload closure, and
|
|
25
|
+
* proves the result with `PRAGMA foreign_key_check`. This module is the thin
|
|
26
|
+
* part: point it at a workspace, ask for a quiescent Session, and seal what
|
|
27
|
+
* comes back with the bundle codec.
|
|
28
|
+
*
|
|
29
|
+
* What it adds on top is the manifest — the Sessions carried, the schema
|
|
30
|
+
* versions the SOURCE database registers, and the connection by slug and model
|
|
31
|
+
* so an importer can resolve it locally. No credential is carried: the state
|
|
32
|
+
* allow-list in the export policy decides what is copied, and configuration is
|
|
33
|
+
* not on it.
|
|
34
|
+
*/
|
|
35
|
+
import { mkdir, mkdtemp, rm, stat } from 'node:fs/promises';
|
|
36
|
+
import { join } from 'node:path';
|
|
37
|
+
import { tmpdir } from 'node:os';
|
|
38
|
+
import { SessionBundleFileError, } from '@maka/storage/session-bundle-contract';
|
|
39
|
+
import { createSessionBundleFileService } from '@maka/storage/session-bundle-file-service';
|
|
40
|
+
import { exportSessionBundleState, SessionBundleExportError, SESSION_BUNDLE_OMITTED_EVENT_TYPES, } from '@maka/storage/session-bundle-policy';
|
|
41
|
+
export const SESSION_EXPORT_MEDIA_TYPE = 'application/vnd.maka.session-export+json;version=1';
|
|
42
|
+
/** Generous by design; the codec is what enforces them. */
|
|
43
|
+
export const SESSION_EXPORT_BUNDLE_LIMITS = {
|
|
44
|
+
maxCompressedBytes: 2 * 1024 ** 3,
|
|
45
|
+
maxDecompressedTarBytes: 8 * 1024 ** 3,
|
|
46
|
+
maxPayloadBytes: 8 * 1024 ** 3,
|
|
47
|
+
maxFileBytes: 2 * 1024 ** 3,
|
|
48
|
+
maxEntryCount: 1_000_000,
|
|
49
|
+
maxManifestBytes: 4 * 1024 ** 2,
|
|
50
|
+
maxStateIdentityBytes: 4 * 1024 ** 2,
|
|
51
|
+
maxPathBytes: 4096,
|
|
52
|
+
maxPathDepth: 32,
|
|
53
|
+
};
|
|
54
|
+
export async function exportSessionBundle(input) {
|
|
55
|
+
if (!(await isDirectory(input.workspaceRoot))) {
|
|
56
|
+
return {
|
|
57
|
+
ok: false,
|
|
58
|
+
reason: { kind: 'workspace_not_found', workspaceRoot: input.workspaceRoot },
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
if (await exists(input.destination)) {
|
|
62
|
+
return { ok: false, reason: { kind: 'destination_exists' } };
|
|
63
|
+
}
|
|
64
|
+
const staging = await mkdtemp(join(tmpdir(), 'maka-session-export-'));
|
|
65
|
+
try {
|
|
66
|
+
const stateRoot = join(staging, 'state');
|
|
67
|
+
const workspaceRoot = join(staging, 'workspace');
|
|
68
|
+
// The codec seals a state tree beside a workspace tree. A Session's own
|
|
69
|
+
// files are the state; the user's project directory is not part of the
|
|
70
|
+
// conversation and is deliberately absent.
|
|
71
|
+
await mkdir(workspaceRoot, { recursive: true });
|
|
72
|
+
let plan;
|
|
73
|
+
try {
|
|
74
|
+
plan = await exportSessionBundleState({
|
|
75
|
+
stateRoot: input.workspaceRoot,
|
|
76
|
+
// Maka's desktop layout keeps state and configuration in one directory,
|
|
77
|
+
// so these are shared on purpose. Configuration stays out of the bundle
|
|
78
|
+
// because the policy copies an allow-list of state entries, not because
|
|
79
|
+
// the roots are apart.
|
|
80
|
+
configRoot: input.workspaceRoot,
|
|
81
|
+
allowShared: true,
|
|
82
|
+
destinationRoot: stateRoot,
|
|
83
|
+
sessionId: input.sessionId,
|
|
84
|
+
requireQuiescent: true,
|
|
85
|
+
includeSubtree: true,
|
|
86
|
+
omitDiagnostics: true,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
catch (error) {
|
|
90
|
+
const failure = asExportFailure(error, input.workspaceRoot);
|
|
91
|
+
if (failure)
|
|
92
|
+
return { ok: false, reason: failure };
|
|
93
|
+
throw error;
|
|
94
|
+
}
|
|
95
|
+
const manifest = {
|
|
96
|
+
format: 'maka.session-export',
|
|
97
|
+
formatVersion: 1,
|
|
98
|
+
exportedAt: (input.now ?? Date.now)(),
|
|
99
|
+
rootSessionId: plan.sessionId,
|
|
100
|
+
sessionIds: plan.sessionIds,
|
|
101
|
+
schema: plan.sourceSchema,
|
|
102
|
+
connection: plan.connection,
|
|
103
|
+
includedEntries: plan.includedEntries,
|
|
104
|
+
excludedEntries: plan.excludedEntries,
|
|
105
|
+
diagnosticsOmitted: true,
|
|
106
|
+
omittedEventTypes: SESSION_BUNDLE_OMITTED_EVENT_TYPES,
|
|
107
|
+
};
|
|
108
|
+
const stateIdentity = {
|
|
109
|
+
mediaType: SESSION_EXPORT_MEDIA_TYPE,
|
|
110
|
+
bytes: Buffer.from(`${JSON.stringify(manifest)}\n`, 'utf8'),
|
|
111
|
+
};
|
|
112
|
+
const artifact = await createSessionBundleFileService().pack({
|
|
113
|
+
snapshot: { stateRoot, workspaceRoot, stateIdentity },
|
|
114
|
+
envelope: { sessionId: plan.sessionId },
|
|
115
|
+
destination: input.destination,
|
|
116
|
+
limits: input.limits ?? SESSION_EXPORT_BUNDLE_LIMITS,
|
|
117
|
+
});
|
|
118
|
+
return { ok: true, artifact, export: manifest };
|
|
119
|
+
}
|
|
120
|
+
catch (error) {
|
|
121
|
+
if (error instanceof SessionBundleFileError) {
|
|
122
|
+
if (error.details?.quota !== undefined) {
|
|
123
|
+
return { ok: false, reason: { kind: 'bundle_limit', details: error.details } };
|
|
124
|
+
}
|
|
125
|
+
// The precheck fails fast, but the packer publishes atomically and can
|
|
126
|
+
// still lose the race. Reporting that as a generic IO failure would give
|
|
127
|
+
// a caller a different exit code for the same outcome.
|
|
128
|
+
if (error.code === 'destination_exists') {
|
|
129
|
+
return { ok: false, reason: { kind: 'destination_exists' } };
|
|
130
|
+
}
|
|
131
|
+
return { ok: false, reason: { kind: 'io_failed', message: error.message } };
|
|
132
|
+
}
|
|
133
|
+
const failure = asExportFailure(error, input.workspaceRoot);
|
|
134
|
+
if (failure)
|
|
135
|
+
return { ok: false, reason: failure };
|
|
136
|
+
return { ok: false, reason: { kind: 'io_failed', message: String(error) } };
|
|
137
|
+
}
|
|
138
|
+
finally {
|
|
139
|
+
await rm(staging, { recursive: true, force: true }).catch(() => { });
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
function asExportFailure(error, workspaceRoot) {
|
|
143
|
+
if (!(error instanceof SessionBundleExportError))
|
|
144
|
+
return undefined;
|
|
145
|
+
switch (error.code) {
|
|
146
|
+
case 'session_active':
|
|
147
|
+
return { kind: 'session_active', message: error.message };
|
|
148
|
+
case 'schema_unsupported':
|
|
149
|
+
return { kind: 'schema_unsupported', message: error.message };
|
|
150
|
+
case 'symlink':
|
|
151
|
+
case 'path_escape':
|
|
152
|
+
case 'unsupported_entry':
|
|
153
|
+
return { kind: 'artifact_unsafe', message: error.message };
|
|
154
|
+
case 'missing_entry':
|
|
155
|
+
return { kind: 'artifact_missing', message: error.message };
|
|
156
|
+
case 'invalid_root':
|
|
157
|
+
// The policy reports a missing Session through this code too, and only
|
|
158
|
+
// the message separates it from a directory that is not a workspace.
|
|
159
|
+
return /session does not exist/i.test(error.message)
|
|
160
|
+
? { kind: 'session_not_found' }
|
|
161
|
+
: { kind: 'workspace_not_found', workspaceRoot };
|
|
162
|
+
case 'destination_not_empty':
|
|
163
|
+
return { kind: 'destination_exists' };
|
|
164
|
+
default:
|
|
165
|
+
return { kind: 'io_failed', message: error.message };
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
async function isDirectory(path) {
|
|
169
|
+
return stat(path)
|
|
170
|
+
.then((metadata) => metadata.isDirectory())
|
|
171
|
+
.catch(() => false);
|
|
172
|
+
}
|
|
173
|
+
async function exists(path) {
|
|
174
|
+
return stat(path)
|
|
175
|
+
.then(() => true)
|
|
176
|
+
.catch(() => false);
|
|
177
|
+
}
|
|
@@ -76,8 +76,25 @@
|
|
|
76
76
|
"./plan-mode": "./dist/plan-mode.js",
|
|
77
77
|
"./plan-tools": "./dist/plan-tools.js",
|
|
78
78
|
"./plugin-composition-loader": "./dist/plugin-composition-loader.js",
|
|
79
|
+
"./plugin-command-service": "./dist/plugin-command-service.js",
|
|
80
|
+
"./plugin-data-services": "./dist/plugin-data-services.js",
|
|
81
|
+
"./plugin-goal-service": "./dist/plugin-goal-service.js",
|
|
82
|
+
"./plugin-lsp-service": "./dist/plugin-lsp-service.js",
|
|
83
|
+
"./plugin-session-query-service": "./dist/plugin-session-query-service.js",
|
|
84
|
+
"./plugin-shell-env-service": "./dist/plugin-shell-env-service.js",
|
|
85
|
+
"./plugin-skill-service": "./dist/plugin-skill-service.js",
|
|
86
|
+
"./plugin-agent-service": "./dist/plugin-agent-service.js",
|
|
87
|
+
"./plugin-attachment-service": "./dist/plugin-attachment-service.js",
|
|
88
|
+
"./plugin-approval-service": "./dist/plugin-approval-service.js",
|
|
89
|
+
"./plugin-fs-service": "./dist/plugin-fs-service.js",
|
|
79
90
|
"./plugin-kernel": "./dist/plugin-kernel.js",
|
|
91
|
+
"./plugin-llm-service": "./dist/plugin-llm-service.js",
|
|
80
92
|
"./plugin-runtime": "./dist/plugin-runtime.js",
|
|
93
|
+
"./plugin-scope-registry": "./dist/plugin-scope-registry.js",
|
|
94
|
+
"./plugin-system-prompt-service": "./dist/plugin-system-prompt-service.js",
|
|
95
|
+
"./plugin-shell-service": "./dist/plugin-shell-service.js",
|
|
96
|
+
"./plugin-user-question-service": "./dist/plugin-user-question-service.js",
|
|
97
|
+
"./plugin-web-service": "./dist/plugin-web-service.js",
|
|
81
98
|
"./plugin-tool-service": "./dist/plugin-tool-service.js",
|
|
82
99
|
"./process-tree-terminator": "./dist/process-tree-terminator.js",
|
|
83
100
|
"./provider-request-telemetry": "./dist/provider-request-telemetry.js",
|
|
@@ -88,6 +105,7 @@
|
|
|
88
105
|
"./sandbox-boundary-tool": "./dist/sandbox-boundary-tool.js",
|
|
89
106
|
"./scheduled-task-tools": "./dist/scheduled-task-tools.js",
|
|
90
107
|
"./session-recap": "./dist/session-recap.js",
|
|
108
|
+
"./session-export": "./dist/session-export.js",
|
|
91
109
|
"./session-trace-projection": "./dist/session-trace-projection.js",
|
|
92
110
|
"./shell-detect": "./dist/shell-detect.js",
|
|
93
111
|
"./shell-run-contract": "./dist/shell-run-contract.js",
|
|
@@ -22,6 +22,7 @@ export function formatHostHandoff(view, locale) {
|
|
|
22
22
|
const tw = locale === 'zh-TW';
|
|
23
23
|
const titles = tw
|
|
24
24
|
? {
|
|
25
|
+
replacement_required: '需要切換 WSL 背景服務',
|
|
25
26
|
busy: '背景服務仍在使用中',
|
|
26
27
|
activity_unknown: '需要確認是否停止背景服務',
|
|
27
28
|
operator_required: '背景服務需要手動更新',
|
|
@@ -30,6 +31,7 @@ export function formatHostHandoff(view, locale) {
|
|
|
30
31
|
}
|
|
31
32
|
: zh
|
|
32
33
|
? {
|
|
34
|
+
replacement_required: '需要切换 WSL 后台服务',
|
|
33
35
|
busy: '后台服务仍在使用中',
|
|
34
36
|
activity_unknown: '需要确认是否停止后台服务',
|
|
35
37
|
operator_required: '后台服务需要手动更新',
|
|
@@ -37,6 +39,7 @@ export function formatHostHandoff(view, locale) {
|
|
|
37
39
|
retry_required: '暂时无法完成交接',
|
|
38
40
|
}
|
|
39
41
|
: {
|
|
42
|
+
replacement_required: 'Switch the WSL background service',
|
|
40
43
|
busy: 'Your background service is still in use',
|
|
41
44
|
activity_unknown: 'Confirm before stopping the service',
|
|
42
45
|
operator_required: 'The background service needs a manual update',
|
|
@@ -45,6 +48,7 @@ export function formatHostHandoff(view, locale) {
|
|
|
45
48
|
};
|
|
46
49
|
const descriptions = tw
|
|
47
50
|
? {
|
|
51
|
+
replacement_required: '目前尚未更新。選擇繼續後,將透過已安裝的管理程式安全停止舊服務並啟動選定版本;有進行中的工作時會另行確認。',
|
|
48
52
|
busy: '其他連線或正在執行的工作阻止了自動交接。停止並繼續可能中斷這些工作。',
|
|
49
53
|
activity_unknown: '背景服務與目前用戶端不相容,且無法確認有哪些工作仍在執行。停止並繼續會重新啟動服務,可能中斷其他視窗或裝置上的工作。',
|
|
50
54
|
operator_required: `目前無法從這裡更新背景服務。請透過 ${view.target.name} 上管理此服務的應用程式或命令更新,再重試。`,
|
|
@@ -53,6 +57,7 @@ export function formatHostHandoff(view, locale) {
|
|
|
53
57
|
}
|
|
54
58
|
: zh
|
|
55
59
|
? {
|
|
60
|
+
replacement_required: '当前尚未更新。选择继续后,将通过已安装的管理程序安全停止旧服务并启动选定版本;有活动工作时会另行确认。',
|
|
56
61
|
busy: '其它连接或正在执行的工作阻止了自动交接。停止并继续可能中断这些工作。',
|
|
57
62
|
activity_unknown: '后台服务与当前客户端不兼容,且无法确认有哪些工作仍在运行。停止并继续会重新启动服务,可能中断其他窗口或设备上的工作。',
|
|
58
63
|
operator_required: `目前无法从这里更新后台服务。请通过 ${view.target.name} 上管理此服务的应用或命令更新,然后重试。`,
|
|
@@ -60,6 +65,7 @@ export function formatHostHandoff(view, locale) {
|
|
|
60
65
|
retry_required: '服务状态发生了变化,或交接尚未完成。可以安全重试,不会默认中断工作。',
|
|
61
66
|
}
|
|
62
67
|
: {
|
|
68
|
+
replacement_required: 'No update is running. Continue to retire the old service through its installed operator and start the selected version. Interrupting active work requires a separate confirmation.',
|
|
63
69
|
busy: 'Other connections or work in progress prevent an automatic handoff. Stopping the service may interrupt that work.',
|
|
64
70
|
activity_unknown: 'The background service is incompatible with this client, and its active work is unknown. Stop and continue restarts it and may interrupt work in other windows or devices.',
|
|
65
71
|
operator_required: `This client cannot update the background service here. Use its managing app or operator command on ${view.target.name}, then retry.`,
|
|
@@ -89,6 +95,28 @@ export function formatHostHandoff(view, locale) {
|
|
|
89
95
|
};
|
|
90
96
|
descriptions.operator_required = guidance[view.recoveryBlocker];
|
|
91
97
|
}
|
|
98
|
+
if (view.manualRecheck && view.reason === 'busy') {
|
|
99
|
+
descriptions.busy = zh
|
|
100
|
+
? tw
|
|
101
|
+
? '舊服務拒絕安全停止;這可能是其他連線或背景工作所致。關閉其他用戶端後可再次嘗試安全停止,或明確選擇中斷工作並繼續。'
|
|
102
|
+
: '旧服务拒绝安全停止;这可能是其他连接或后台工作所致。关闭其他客户端后可再次尝试安全停止,或明确选择中断工作并继续。'
|
|
103
|
+
: 'The old service refused safe retirement. Other connections or background work may be keeping it in use. Close other clients and try stopping safely again, or explicitly interrupt work and continue.';
|
|
104
|
+
}
|
|
105
|
+
if (view.manualRecheck && view.reason === 'operator_required') {
|
|
106
|
+
titles.operator_required = zh
|
|
107
|
+
? tw
|
|
108
|
+
? '無法連線到背景服務'
|
|
109
|
+
: '无法连接后台服务'
|
|
110
|
+
: 'Cannot connect to the background service';
|
|
111
|
+
descriptions.operator_required = zh
|
|
112
|
+
? tw
|
|
113
|
+
? '目前版本無法連線,且尚未執行更新。請查看診斷或更新用戶端,完成後重新檢查。'
|
|
114
|
+
: '当前版本无法连接,且尚未执行更新。请查看诊断或更新客户端,完成后重新检查。'
|
|
115
|
+
: 'These builds cannot connect and no update is running. Check the diagnostic or update the client, then recheck.';
|
|
116
|
+
}
|
|
117
|
+
const packageChange = view.packageChange
|
|
118
|
+
? `${view.target.name}: ${view.packageChange.current} → ${view.packageChange.target}`
|
|
119
|
+
: '';
|
|
92
120
|
const activity = view.activity;
|
|
93
121
|
const background = activity?.drainResidencies;
|
|
94
122
|
const backgroundFacts = !activity
|
|
@@ -111,17 +139,23 @@ export function formatHostHandoff(view, locale) {
|
|
|
111
139
|
? `${activity.connections} 个连接 · ${activity.activeOperations} 个进行中的操作`
|
|
112
140
|
: `${activity.connections} connections · ${activity.activeOperations} operations in progress`
|
|
113
141
|
: '';
|
|
114
|
-
const waiting = view.
|
|
115
|
-
?
|
|
116
|
-
?
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
:
|
|
121
|
-
?
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
142
|
+
const waiting = view.manualRecheck
|
|
143
|
+
? zh
|
|
144
|
+
? tw
|
|
145
|
+
? '重新檢查只會重試連線,不會更新服務。'
|
|
146
|
+
: '重新检查只会重试连接,不会更新服务。'
|
|
147
|
+
: 'Recheck retries the connection without updating the service.'
|
|
148
|
+
: view.mayExitNaturally
|
|
149
|
+
? tw
|
|
150
|
+
? 'Maka 會持續檢查,並在可以安全繼續時自動繼續。'
|
|
151
|
+
: zh
|
|
152
|
+
? 'Maka 会持续检查,并在可以安全继续时自动继续。'
|
|
153
|
+
: 'Maka keeps checking and continues automatically when it is safe.'
|
|
154
|
+
: tw
|
|
155
|
+
? 'Maka 會持續檢查。若狀態沒有改變,等待或重試不會解決此問題。'
|
|
156
|
+
: zh
|
|
157
|
+
? 'Maka 会持续检查。若状态没有变化,等待或重试不会解决此问题。'
|
|
158
|
+
: 'Maka keeps checking. Waiting or retrying will not resolve this unless the service state changes.';
|
|
125
159
|
const repairNotice = view.operation === 'repair'
|
|
126
160
|
? tw
|
|
127
161
|
? '修復將使用目前應用程式配套的版本,可能替換現有服務版本。'
|
|
@@ -132,19 +166,34 @@ export function formatHostHandoff(view, locale) {
|
|
|
132
166
|
const labels = tw
|
|
133
167
|
? {
|
|
134
168
|
cancel: '取消',
|
|
135
|
-
retry: '安全重試',
|
|
136
|
-
|
|
169
|
+
retry: view.manualRecheck ? '重新檢查' : '安全重試',
|
|
170
|
+
replace: '停止舊服務並繼續',
|
|
171
|
+
interrupt: view.manualRecheck
|
|
172
|
+
? '中斷工作並繼續'
|
|
173
|
+
: view.operation === 'repair'
|
|
174
|
+
? '中斷並修復'
|
|
175
|
+
: '停止並繼續',
|
|
137
176
|
}
|
|
138
177
|
: zh
|
|
139
178
|
? {
|
|
140
179
|
cancel: '取消',
|
|
141
|
-
retry: '安全重试',
|
|
142
|
-
|
|
180
|
+
retry: view.manualRecheck ? '重新检查' : '安全重试',
|
|
181
|
+
replace: '停止旧服务并继续',
|
|
182
|
+
interrupt: view.manualRecheck
|
|
183
|
+
? '中断工作并继续'
|
|
184
|
+
: view.operation === 'repair'
|
|
185
|
+
? '中断并修复'
|
|
186
|
+
: '停止并继续',
|
|
143
187
|
}
|
|
144
188
|
: {
|
|
145
189
|
cancel: 'Cancel',
|
|
146
|
-
retry: 'Retry safely',
|
|
147
|
-
|
|
190
|
+
retry: view.manualRecheck ? 'Recheck' : 'Retry safely',
|
|
191
|
+
replace: 'Stop old service and continue',
|
|
192
|
+
interrupt: view.manualRecheck
|
|
193
|
+
? 'Interrupt work and continue'
|
|
194
|
+
: view.operation === 'repair'
|
|
195
|
+
? 'Interrupt and repair'
|
|
196
|
+
: 'Stop and continue',
|
|
148
197
|
};
|
|
149
198
|
const phases = tw
|
|
150
199
|
? {
|
|
@@ -187,7 +236,7 @@ export function formatHostHandoff(view, locale) {
|
|
|
187
236
|
: zh
|
|
188
237
|
? '正在完成交接或安全收尾,请稍候。'
|
|
189
238
|
: 'Finishing the handoff or its safe recovery. Please wait.'
|
|
190
|
-
: [facts, backgroundFacts, waiting, repairNotice, view.operatorStep]
|
|
239
|
+
: [packageChange, facts, backgroundFacts, waiting, repairNotice, view.operatorStep]
|
|
191
240
|
.filter(Boolean)
|
|
192
241
|
.join('\n'),
|
|
193
242
|
actions: view.actions.map((action) => ({ action, label: labels[action] })),
|
|
@@ -55,6 +55,8 @@ export async function runHostHandoff(input) {
|
|
|
55
55
|
// (or a broken successor) into an automatic replacement storm.
|
|
56
56
|
const attempted = new Set();
|
|
57
57
|
let automaticAttempts = 0;
|
|
58
|
+
let refusedWork;
|
|
59
|
+
let replacementCompleted = false;
|
|
58
60
|
let recovery;
|
|
59
61
|
const publish = (next) => {
|
|
60
62
|
view = next;
|
|
@@ -102,7 +104,10 @@ export async function runHostHandoff(input) {
|
|
|
102
104
|
target: view.target,
|
|
103
105
|
reason: 'unavailable',
|
|
104
106
|
mayExitNaturally: false,
|
|
105
|
-
|
|
107
|
+
...(view.manualRecheck ? { manualRecheck: true } : {}),
|
|
108
|
+
diagnostic: replacementCompleted
|
|
109
|
+
? `Host replacement completed, but reconnection failed: ${boundedDiagnostic(error)}`
|
|
110
|
+
: boundedDiagnostic(error),
|
|
106
111
|
},
|
|
107
112
|
};
|
|
108
113
|
}
|
|
@@ -124,11 +129,13 @@ export async function runHostHandoff(input) {
|
|
|
124
129
|
const blocker = observed.blocker;
|
|
125
130
|
if (recovery?.identity !== blocker.identity)
|
|
126
131
|
recovery = undefined;
|
|
132
|
+
if (refusedWork !== blocker.identity)
|
|
133
|
+
refusedWork = undefined;
|
|
127
134
|
const nextSignature = blockerSignature(blocker);
|
|
128
135
|
if (signature !== nextSignature) {
|
|
129
136
|
signature = nextSignature;
|
|
130
137
|
choice = undefined;
|
|
131
|
-
publish(projectBlocker(blocker, randomUUID(), recovery?.diagnostic));
|
|
138
|
+
publish(projectBlocker(blocker, randomUUID(), recovery?.diagnostic, refusedWork === blocker.identity));
|
|
132
139
|
}
|
|
133
140
|
const current = view;
|
|
134
141
|
const selected = choice;
|
|
@@ -142,12 +149,19 @@ export async function runHostHandoff(input) {
|
|
|
142
149
|
const automatic = !recovery &&
|
|
143
150
|
automaticAttempts < 3 &&
|
|
144
151
|
!attempted.has(nextSignature) &&
|
|
152
|
+
!blocker.replacement?.requiresExplicitSelection &&
|
|
145
153
|
blocker.replacement?.canReplaceIdle &&
|
|
146
154
|
blocker.activity &&
|
|
147
155
|
(isHostActivityIdle(blocker.activity) || cooperative);
|
|
148
156
|
const interrupt = selected?.revision === current.revision && selected.action === 'interrupt';
|
|
157
|
+
const replace = selected?.revision === current.revision && selected.action === 'replace';
|
|
149
158
|
const retry = selected?.revision === current.revision && selected.action === 'retry';
|
|
150
|
-
if ((automatic ||
|
|
159
|
+
if ((automatic ||
|
|
160
|
+
interrupt ||
|
|
161
|
+
replace ||
|
|
162
|
+
(retry &&
|
|
163
|
+
!blocker.replacement?.requiresExplicitSelection &&
|
|
164
|
+
blocker.replacement?.canReplaceIdle)) &&
|
|
151
165
|
blocker.replacement) {
|
|
152
166
|
if (automatic) {
|
|
153
167
|
attempted.add(nextSignature);
|
|
@@ -179,6 +193,8 @@ export async function runHostHandoff(input) {
|
|
|
179
193
|
finally {
|
|
180
194
|
attemptAbort = undefined;
|
|
181
195
|
}
|
|
196
|
+
replacementCompleted = result.kind === 'completed';
|
|
197
|
+
refusedWork = result.kind === 'active_work' ? blocker.identity : undefined;
|
|
182
198
|
recovery =
|
|
183
199
|
result.kind === 'recovery_required'
|
|
184
200
|
? { identity: blocker.identity, diagnostic: result.diagnostic.slice(0, 8_192) }
|
|
@@ -195,7 +211,7 @@ export async function runHostHandoff(input) {
|
|
|
195
211
|
signature = undefined;
|
|
196
212
|
continue;
|
|
197
213
|
}
|
|
198
|
-
const attention = projectBlocker(blocker, current.revision, recovery?.diagnostic);
|
|
214
|
+
const attention = projectBlocker(blocker, current.revision, recovery?.diagnostic, refusedWork === blocker.identity);
|
|
199
215
|
if (!input.openSurface)
|
|
200
216
|
throw new HostHandoffRequiredError(attention);
|
|
201
217
|
surface ??= input.openSurface(submit);
|
|
@@ -211,7 +227,7 @@ export async function runHostHandoff(input) {
|
|
|
211
227
|
resolve();
|
|
212
228
|
};
|
|
213
229
|
const abort = () => finish(input.signal?.reason);
|
|
214
|
-
const timer = setTimeout(finish, interval);
|
|
230
|
+
const timer = blocker.manualRecheck ? undefined : setTimeout(finish, interval);
|
|
215
231
|
wake = () => finish();
|
|
216
232
|
input.signal?.addEventListener('abort', abort, { once: true });
|
|
217
233
|
if (input.signal?.aborted)
|
|
@@ -225,7 +241,7 @@ export async function runHostHandoff(input) {
|
|
|
225
241
|
surface?.close();
|
|
226
242
|
}
|
|
227
243
|
}
|
|
228
|
-
function projectBlocker(blocker, revision, recovery) {
|
|
244
|
+
function projectBlocker(blocker, revision, recovery, activeWorkRefused = false) {
|
|
229
245
|
const replacement = blocker.replacement;
|
|
230
246
|
return {
|
|
231
247
|
revision,
|
|
@@ -237,15 +253,30 @@ function projectBlocker(blocker, revision, recovery) {
|
|
|
237
253
|
? 'repair_required'
|
|
238
254
|
: !replacement
|
|
239
255
|
? 'operator_required'
|
|
240
|
-
:
|
|
241
|
-
?
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
256
|
+
: replacement.requiresExplicitSelection
|
|
257
|
+
? activeWorkRefused
|
|
258
|
+
? 'busy'
|
|
259
|
+
: 'replacement_required'
|
|
260
|
+
: !blocker.activity
|
|
261
|
+
? 'activity_unknown'
|
|
262
|
+
: isHostActivityIdle(blocker.activity)
|
|
263
|
+
? 'retry_required'
|
|
264
|
+
: 'busy',
|
|
245
265
|
...(blocker.activity ? { activity: blocker.activity } : {}),
|
|
246
266
|
mayExitNaturally: blocker.mayExitNaturally,
|
|
267
|
+
...(blocker.manualRecheck ? { manualRecheck: true } : {}),
|
|
268
|
+
...(blocker.packageChange ? { packageChange: blocker.packageChange } : {}),
|
|
247
269
|
...(replacement ? { operation: replacement.kind } : {}),
|
|
248
|
-
actions: replacement?.
|
|
270
|
+
actions: replacement?.requiresExplicitSelection
|
|
271
|
+
? [
|
|
272
|
+
'cancel',
|
|
273
|
+
'retry',
|
|
274
|
+
'replace',
|
|
275
|
+
...(activeWorkRefused && replacement.canInterrupt ? ['interrupt'] : []),
|
|
276
|
+
]
|
|
277
|
+
: replacement?.canInterrupt
|
|
278
|
+
? ['cancel', 'retry', 'interrupt']
|
|
279
|
+
: ['cancel', 'retry'],
|
|
249
280
|
defaultAction: 'cancel',
|
|
250
281
|
...(blocker.recoveryBlocker ? { recoveryBlocker: blocker.recoveryBlocker } : {}),
|
|
251
282
|
...(blocker.operatorStep ? { operatorStep: blocker.operatorStep } : {}),
|
|
@@ -265,6 +296,9 @@ function blockerSignature(blocker) {
|
|
|
265
296
|
blocker.replacement?.kind,
|
|
266
297
|
blocker.replacement?.canReplaceIdle,
|
|
267
298
|
blocker.replacement?.canInterrupt,
|
|
299
|
+
blocker.replacement?.requiresExplicitSelection,
|
|
300
|
+
blocker.manualRecheck,
|
|
301
|
+
blocker.packageChange,
|
|
268
302
|
blocker.operatorStep,
|
|
269
303
|
blocker.recoveryBlocker,
|
|
270
304
|
blocker.diagnostic,
|
|
@@ -41,6 +41,29 @@ const frameBase = {
|
|
|
41
41
|
schemaVersion: z.literal(1),
|
|
42
42
|
sequence: z.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER),
|
|
43
43
|
};
|
|
44
|
+
const environmentBindingFields = {
|
|
45
|
+
version: boundedString(128),
|
|
46
|
+
serviceId: z.string().regex(/^[a-f0-9]{64}$/u),
|
|
47
|
+
deploymentId: z.string().uuid(),
|
|
48
|
+
operator: z.unknown().transform((value, context) => {
|
|
49
|
+
try {
|
|
50
|
+
const command = decodeRuntimeHostOperatorCommand(value);
|
|
51
|
+
if (command.kind !== 'node') {
|
|
52
|
+
throw new Error('Runtime Host setup operator must be a Node command');
|
|
53
|
+
}
|
|
54
|
+
return command;
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
context.addIssue({
|
|
58
|
+
code: 'custom',
|
|
59
|
+
message: error instanceof Error ? error.message : 'Runtime Host operator is invalid',
|
|
60
|
+
});
|
|
61
|
+
return z.NEVER;
|
|
62
|
+
}
|
|
63
|
+
}),
|
|
64
|
+
rootPath: boundedString(4 * 1024).refine((value) => !/[\u0000-\u001f\u007f]/u.test(value)),
|
|
65
|
+
rootId: z.string().regex(/^[a-f0-9]{64}$/u),
|
|
66
|
+
};
|
|
44
67
|
const SETUP_FRAME_SCHEMA = z.discriminatedUnion('kind', [
|
|
45
68
|
z
|
|
46
69
|
.object({
|
|
@@ -53,27 +76,7 @@ const SETUP_FRAME_SCHEMA = z.discriminatedUnion('kind', [
|
|
|
53
76
|
.object({
|
|
54
77
|
...frameBase,
|
|
55
78
|
kind: z.literal('complete'),
|
|
56
|
-
|
|
57
|
-
serviceId: z.string().regex(/^[a-f0-9]{64}$/u),
|
|
58
|
-
deploymentId: z.string().uuid(),
|
|
59
|
-
operator: z.unknown().transform((value, context) => {
|
|
60
|
-
try {
|
|
61
|
-
const command = decodeRuntimeHostOperatorCommand(value);
|
|
62
|
-
if (command.kind !== 'node') {
|
|
63
|
-
throw new Error('Runtime Host setup operator must be a Node command');
|
|
64
|
-
}
|
|
65
|
-
return command;
|
|
66
|
-
}
|
|
67
|
-
catch (error) {
|
|
68
|
-
context.addIssue({
|
|
69
|
-
code: 'custom',
|
|
70
|
-
message: error instanceof Error ? error.message : 'Runtime Host operator is invalid',
|
|
71
|
-
});
|
|
72
|
-
return z.NEVER;
|
|
73
|
-
}
|
|
74
|
-
}),
|
|
75
|
-
rootPath: boundedString(4 * 1024).refine((value) => !/[\u0000-\u001f\u007f]/u.test(value)),
|
|
76
|
-
rootId: z.string().regex(/^[a-f0-9]{64}$/u),
|
|
79
|
+
...environmentBindingFields,
|
|
77
80
|
endpoint: boundedString(SETUP_FIELD_MAX_BYTES).refine((value) => parseRuntimeHostSetupEndpoint(value) !== undefined),
|
|
78
81
|
credentialId: boundedString(SETUP_FIELD_MAX_BYTES),
|
|
79
82
|
credential: boundedString(SETUP_CREDENTIAL_MAX_BYTES),
|
|
@@ -87,6 +90,13 @@ const SETUP_FRAME_SCHEMA = z.discriminatedUnion('kind', [
|
|
|
87
90
|
.optional(),
|
|
88
91
|
})
|
|
89
92
|
.strict(),
|
|
93
|
+
z
|
|
94
|
+
.object({
|
|
95
|
+
...frameBase,
|
|
96
|
+
kind: z.literal('existing_environment'),
|
|
97
|
+
...environmentBindingFields,
|
|
98
|
+
})
|
|
99
|
+
.strict(),
|
|
90
100
|
z
|
|
91
101
|
.object({
|
|
92
102
|
...frameBase,
|
|
@@ -62,7 +62,15 @@ export const RUNTIME_HOST_REGISTRATION_SCHEMA_VERSION = 1;
|
|
|
62
62
|
export const RUNTIME_HOST_PROTOCOL_VERSION = 0;
|
|
63
63
|
// Increment when the same protocol version no longer guarantees safe Client-Host
|
|
64
64
|
// interoperability. Mismatches are rejected before domain commands are admitted.
|
|
65
|
-
export const RUNTIME_HOST_COMPATIBILITY_EPOCH =
|
|
65
|
+
export const RUNTIME_HOST_COMPATIBILITY_EPOCH = 141;
|
|
66
|
+
// 141: WorkHub root admissions bind model Intent/Recall decisions before actions.
|
|
67
|
+
// 140: Plugin Platform queries expose scoped Command contribution projections.
|
|
68
|
+
// Epoch-139 peers reject the added query view and result shape.
|
|
69
|
+
// 139: WorkHub recovery preserves the Host-authenticated Desktop capability binding.
|
|
70
|
+
// 138: Removes the unused steering display anchor from canonical MessageContent.
|
|
71
|
+
// 137: Reserved by the former display anchor contract.
|
|
72
|
+
// 136: WorkHub transient proposals distinguish routing dispositions from linked
|
|
73
|
+
// operations. Older peers expect replace/stop_work/resume_work dispositions.
|
|
66
74
|
// 135: WorkHub model Turns replace direct action proposals with active-Turn task tools.
|
|
67
75
|
// 134: Coordination actions own real Runtime Turns. Removes the synthetic record
|
|
68
76
|
// operation, projects typed action receipts and admitted action identities, and
|