@entrorove/skill 0.1.43 → 0.1.48

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 CHANGED
@@ -1,5 +1,30 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.48 — 2026-10-16
4
+
5
+ - 新增标准命令 `install --host codex`,仅配置 Codex 并保留 Claude 的配置与技能副本;后续自动更新沿用已配置的宿主范围。安装命令守卫和旧版清理也按目标宿主限界。
6
+ - Added the standard `install --host codex` command to configure Codex while preserving Claude configuration and skill copies. Later automatic updates retain the already configured host scope; installer command guards and obsolete-install cleanup honor the selected host.
7
+
8
+ ## 0.1.47 — 2026-10-16
9
+
10
+ - 修复失败 flow 的取消、放弃和会话解绑;离线 unlock 仅恢复当前会话并保留任务队列。放行授权根或依赖目录内的只读 tsconfig 引用,越界错误说明原路径、解析结果和原因。升级时清理未改动的旧受管安装,保留用户编辑及其他任务;IDE 钩子仅对已绑定的会话和仓库生效。
11
+ - Fixed cancellation, abandonment, and session unbinding for failed flows; offline unlock restores only the current session and preserves its queue. Read-only tsconfig references inside the authorized root or dependency directories are accepted, while escape errors identify the input path, resolved path, and reason. Upgrades remove pristine obsolete managed installs while preserving user edits and unrelated tasks; IDE hooks apply only to a bound session and repository.
12
+
13
+ ## 0.1.46 — 2026-10-16
14
+
15
+ - 更新了Codex 后续轮次工具门禁、IDE 执行与复用纪律文档、技能运行和受管安装、IDE 工作流程说明。
16
+ - Updated Codex tool guards across turns, IDE execution and reuse discipline documentation, skill runtime and managed installation, IDE workflow instructions.
17
+
18
+ ## 0.1.45 — 2026-10-16
19
+
20
+ - 更新了原链同流程技能调用。
21
+ - Updated original-chain skill calls on the same workflow.
22
+
23
+ ## 0.1.44 — 2026-10-16
24
+
25
+ - 更新了Calctool 审计测试运维真实检查与独立最终闸门、自动版本与更新日志。
26
+ - Updated Calctool audit, test, operations checks and independent final gate, automatic versioning and changelog.
27
+
3
28
  ## 0.1.43 — 2026-10-16
4
29
 
5
30
  - 更新了Codex 后续轮次工具门禁、复用与变更一致性门禁、IDE 执行与复用纪律文档、技能运行和受管安装、旧版受管安装清理、IDE 工作流程说明、任务交付边界说明、安装包配置、自动版本与更新日志、本地三轴读取预算、本地阶段边界与可恢复停止、远端恢复客户端、账号沙箱偏好与任务入口确认、工作流本地调用契约。
package/IDE-EXECUTION.md CHANGED
@@ -1,8 +1,8 @@
1
- Install or update the managed copy with `npx @entrorove/skill install`. The installer keeps one global managed entrorove skill, replaces its older pristine version, removes only pristine duplicate installations, and preserves hand-edited files. An older package cannot overwrite a newer managed version.
1
+ Install or update the managed copy with `npx @entrorove/skill install`. In Codex, use `npx @entrorove/skill install --host codex` to configure Codex only; this leaves Claude Code hook settings and skill copies alone. The installer keeps one global managed entrorove skill, replaces its older pristine version, removes only pristine duplicate installations for the selected host, and preserves hand-edited files. An older package cannot overwrite a newer managed version.
2
2
 
3
3
  Register or sign in by email at https://entrorove.com. Enter the account key once with `npx @entrorove/skill configure` and `{"token":"..."}` on standard input. The credential persists in the current OS user's restricted local store across skill upgrades. Before asking again, use the stored credential; a missing or invalid credential must be reported explicitly. Never place the key in arguments, URLs, project files or logs. entrorove accounts and keys are independent of other services.
4
4
 
5
- If the IDE guard itself is the blocked step, run `entrorove unlock` directly from the installed CLI. This break-glass command is offline and management-only: it does not call a hook, read a credential, accept the agreement, inspect the protocol or authorize project writes. Use the resulting management plane to inspect or terminate the original flow; `flow-abandon` releases its task binding so the original task can start again.
5
+ If the IDE guard itself is the blocked step, run `entrorove unlock` directly from the affected IDE terminal. The command is offline and records a 15-minute lease bound to that IDE session; it does not call a hook, read a credential, accept the agreement, inspect the protocol or authorize project writes. The host session ID must be present in the environment or supplied as `ENTROROVE_SESSION`. Under the lease, `flow-cancel` and `flow-abandon` recover the original flow; successful `flow-abandon` releases its task binding in both the flow state and the IDE session record, even without a hook callback. Queued requests remain, and other IDE sessions stay guarded.
6
6
 
7
7
  To stop using Entrorove on this computer, run `entrorove uninstall` directly. It works offline, removes only the recorded Entrorove host hooks and pristine global skill, and leaves other host settings, credentials and task records in place. A hand-edited file is reported as preserved rather than deleted. This command does not inspect or change another project.
8
8
 
package/SKILL.md CHANGED
@@ -5,14 +5,14 @@ description: Execute an engineering task through Entrorove, preserving its origi
5
5
 
6
6
  # entrorove · 熵巡
7
7
 
8
- Version: 0.1.43.
8
+ Version: 0.1.48.
9
9
 
10
10
  Entrorove applies to the whole task once selected. Reading this document or calling an entry once does not execute the task. On every later turn, including after compaction, continue the original task and its required next operation before dependent work. Keep the original goal, scope, account, flow, cloud task and failure records. Never describe planning, a reported step or model output as verified delivery.
11
11
 
12
12
  ## Required execution
13
13
 
14
- 1. Install `npx --yes --prefer-online @entrorove/skill@latest install` only when the user chooses to enable this skill. The managed installer writes the skill and IDE lifecycle hook configuration; confirm that the IDE trusts it and observe a real callback before describing enforcement as active. Do not ask the user to type a nonexistent `/hooks` command. If the host reports a blocked management call, show and allow the offline `entrorove unlock` command; it only opens the skill management plane and never grants project-write authority. New execution from a detected IDE requires the exact live host approval. An unsupported IDE adapter, missing hook callback, missing credential, or failed discovery stops execution explicitly. Do not silently use native tools instead. The guard is host-agnostic: one managed hook is installed once, and each host is described by its own adapter (config location, event names, entry shape). Codex and Claude Code are the verified adapters today; a host with no verified adapter, or a run whose host session identity is missing, is refused explicitly rather than degrading to native tools.
15
- `entrorove unlock` is the break-glass path: run the installed CLI directly in a terminal. It writes a short-lived management-only lease locally, without a hook callback, network request, credential read, agreement check or protocol lookup. It never authorizes project writes; use it only to recover the management plane and then continue or terminate the original flow.
14
+ 1. Install `npx --yes --prefer-online @entrorove/skill@latest install --host codex` in Codex only when the user chooses to enable this skill. The managed installer writes the skill and Codex lifecycle hook configuration without changing Claude Code settings; confirm that the IDE trusts it and observe a real callback before describing enforcement as active. Do not ask the user to type a nonexistent `/hooks` command. If the host reports a blocked management call, show and allow the offline `entrorove unlock` command; it only opens the skill management plane and never grants project-write authority. New execution from a detected IDE requires the exact live host approval. An unsupported IDE adapter, missing hook callback, missing credential, or failed discovery stops execution explicitly. Do not silently use native tools instead. The guard is host-agnostic: one managed hook is installed once, and each host is described by its own adapter (config location, event names, entry shape). Codex and Claude Code are the verified adapters today; a host with no verified adapter, or a run whose host session identity is missing, is refused explicitly rather than degrading to native tools.
15
+ `entrorove unlock` is the break-glass path: run the installed CLI directly in the affected IDE terminal. It writes a short-lived lease for that IDE session only, without a hook callback, network request, credential read, agreement check or protocol lookup. The current session ID must be available from the host environment or `ENTROROVE_SESSION`. The lease permits `flow-cancel` and `flow-abandon` recovery, never project writes or another session's calls. A successful `flow-abandon` clears that session's old binding locally even without a hook callback and preserves its queued requests.
16
16
  To remove this skill and its host hooks, run `entrorove uninstall` directly. It works offline and preserves unrelated host settings, credentials and task records. If a managed file was edited, it reports the preserved path instead of deleting it.
17
17
  2. Read [IDE-EXECUTION.md](IDE-EXECUTION.md) for the exact entry, cloud callbacks, account agreement and operation contracts. Use the stored account credential; never put it in arguments, URLs, project files, output or fixtures. Entrorove is independent of other systems.
18
18
  3. Discover `capabilities`, then invoke `start` with the actual goal, task type, risk and scope facts. An enabled account returns a cloud choice before charging a model. Show that choice visibly using the host question tool. Confirmation calls its original callback; exit adopts the local route for this task. Never treat silence as permission, invent acceptance, or create both routes. Existing explicit authorization for this task persists.
package/break-glass.mjs CHANGED
@@ -1,10 +1,16 @@
1
1
  import { promises as fs } from 'node:fs';
2
- import { randomUUID } from 'node:crypto';
2
+ import { createHash, randomUUID } from 'node:crypto';
3
3
  import os from 'node:os';
4
4
  import path from 'node:path';
5
5
 
6
6
  export const breakGlassSchema = 'entrorove.ide-break-glass/1.0';
7
7
  export const breakGlassTtlMs = 15 * 60 * 1000;
8
+ export function breakGlassSessionDigest(sessionId) {
9
+ if (typeof sessionId !== 'string' || !/^[A-Za-z0-9_-]{1,200}$/u.test(sessionId)) {
10
+ throw new Error('Break-glass requires the current IDE session identity');
11
+ }
12
+ return createHash('sha256').update(sessionId).digest('hex');
13
+ }
8
14
 
9
15
  function homeDirectory(home) {
10
16
  if (typeof home !== 'string' || !path.isAbsolute(home)) throw new Error('Break-glass home must be absolute');
@@ -38,11 +44,12 @@ export function breakGlassPath(home = os.homedir()) {
38
44
  return path.join(homeDirectory(home), 'break-glass.json');
39
45
  }
40
46
 
41
- export async function writeBreakGlass(home = os.homedir(), now = Date.now()) {
47
+ export async function writeBreakGlass(home = os.homedir(), now = Date.now(), sessionId) {
42
48
  if (!Number.isFinite(now)) throw new Error('Break-glass clock is invalid');
49
+ const sessionDigest = breakGlassSessionDigest(sessionId);
43
50
  const file = breakGlassPath(home), directory = path.dirname(file);
44
51
  await ensurePrivateDirectory(directory, home);
45
- const value = { schemaVersion: breakGlassSchema, mode: 'management-only', createdAt: new Date(now).toISOString(),
52
+ const value = { schemaVersion: breakGlassSchema, mode: 'management-only', sessionDigest, createdAt: new Date(now).toISOString(),
46
53
  expiresAt: new Date(now + breakGlassTtlMs).toISOString() };
47
54
  const stage = `${file}.${randomUUID()}.tmp`;
48
55
  await fs.writeFile(stage, `${JSON.stringify(value)}\n`, { flag: 'wx', mode: 0o600 });
package/cli.mjs CHANGED
@@ -17,9 +17,9 @@ import { buildReview, submitSkillFeedback, feedbackNoticeVersion } from './revie
17
17
  import { queryCloudTask, runCloudTask as dispatchCloudTask } from './cloud-task-client.mjs';
18
18
  import { watchCloudTask } from './cloud-watch-client.mjs';
19
19
  import { queryAutomaticFeedback } from './automatic-feedback-client.mjs';
20
- import { activeIdeAdapters, installIdeHooks, uninstallIdeHooks } from './ide-install.mjs';
20
+ import { activeIdeAdapters, ideAdapters, installIdeHooks, uninstallIdeHooks } from './ide-install.mjs';
21
21
  import { cleanManagedDuplicates } from './install-cleanup.mjs';
22
- import { assertIdeExecution } from './ide-session.mjs';
22
+ import { assertIdeExecution, ideGuardBinding, settleBreakGlassSession } from './ide-session.mjs';
23
23
  import { writeBreakGlass } from './break-glass.mjs';
24
24
  const markerName = '.entrorove-managed.json';
25
25
  const installFiles = ['SKILL.md', 'CAPABILITY-MAP.md', 'AGREEMENT.md', 'manifest.json', 'IDE-EXECUTION.md', 'REUSE-DISCIPLINE.md', 'DELIVERY-BOUNDARIES.md'];
@@ -246,7 +246,9 @@ async function updateGlobalInstall(target, bundle) {
246
246
  return true;
247
247
  }
248
248
 
249
- export async function install({ home = os.homedir(), cwd = process.cwd(), explicit = false } = {}) {
249
+ export async function install({ home = os.homedir(), cwd = process.cwd(), explicit = false, hostId } = {}) {
250
+ const selected = hostId === undefined ? null : ideAdapters.find(adapter => adapter.id === hostId);
251
+ if (hostId !== undefined && hostId !== 'codex') throw new Error('Install host must be codex');
250
252
  const target = globalInstallPath(home);
251
253
  if (!explicit && !(await managedState(target))) return { installed: false, updated: false };
252
254
  const parent = path.dirname(target);
@@ -259,8 +261,10 @@ export async function install({ home = os.homedir(), cwd = process.cwd(), explic
259
261
  const updated = await updateGlobalInstall(target, bundle);
260
262
  const { removed, preserved } = await cleanManagedDuplicates({
261
263
  target, cwd, home, isManaged: managedState, includeProject: explicit,
264
+ hostIds: selected ? [selected.id] : active?.map(adapter => adapter.id),
262
265
  });
263
- const ide = await installIdeHooks({ home, version: bundle.manifest.version, adapters: active });
266
+ const ide = await installIdeHooks({ home, version: bundle.manifest.version,
267
+ adapters: selected ? [selected] : active });
264
268
  return { version: bundle.manifest.version, updated, removed, preserved, agreementStale: stale, ide };
265
269
  });
266
270
  }
@@ -388,7 +392,9 @@ async function invoke(data) {
388
392
  if (data?.operation === 'cloud-task') return runCloudTask(data.input, remoteDependencies());
389
393
  if (data?.operation === 'cloud-status') return runCloudStatus(data.input, remoteDependencies());
390
394
  if (data?.operation === 'cloud-watch') return runCloudWatch(data.input);
391
- return invokeRemote(data, remoteDependencies());
395
+ const result = await invokeRemote(data, remoteDependencies());
396
+ await settleBreakGlassSession(['invoke'], data, result);
397
+ return result;
392
398
  }
393
399
 
394
400
  export { buildReview };
@@ -422,13 +428,13 @@ async function runAccountCommand(command, args) {
422
428
  async function main() {
423
429
  const [command, ...args] = process.argv.slice(2);
424
430
  if (command === 'unlock' && args.length === 0) {
425
- process.stdout.write(`${JSON.stringify(await writeBreakGlass())}\n`);
431
+ process.stdout.write(`${JSON.stringify(await writeBreakGlass(os.homedir(), Date.now(), ideGuardBinding().sessionId))}\n`);
426
432
  } else if (command === 'uninstall' && args.length === 0) {
427
433
  const result = await uninstall();
428
434
  process.stdout.write(`${JSON.stringify(result)}\n`);
429
435
  if (!result.uninstalled) process.exitCode = 2;
430
- } else if (command === 'install' && args.length === 0) {
431
- process.stdout.write(JSON.stringify(await install({ explicit: true })) + '\n');
436
+ } else if (command === 'install' && (args.length === 0 || args.length === 2 && args[0] === '--host')) {
437
+ process.stdout.write(JSON.stringify(await install({ explicit: true, hostId: args[1] })) + '\n');
432
438
  } else if (command === 'configure' && args.length === 0) {
433
439
  const data = await stdinJson();
434
440
  if (Object.keys(data).length !== 1 || typeof data.token !== 'string') throw new Error('Expected {"token":"..."} on stdin');
@@ -473,7 +479,7 @@ async function main() {
473
479
  } else if (command === 'local') {
474
480
  await runLocal(args);
475
481
  } else {
476
- throw new Error('Usage: entrorove unlock | uninstall | install | configure | agreement | invoke | queue | native <domain> <operation> | notifications configure|status|test | scope-connect <repositoryRoot> | host-message [adapterModulePath] | cloud-task | cloud-watch | cloud-status | feedback <requestId> [generation] | status <requestId> | flows | usage [flowId] | billing | recurring list|claim|finish|runs | review | local (JSON on stdin)');
482
+ throw new Error('Usage: entrorove unlock | uninstall | install [--host codex] | configure | agreement | invoke | queue | native <domain> <operation> | notifications configure|status|test | scope-connect <repositoryRoot> | host-message [adapterModulePath] | cloud-task | cloud-watch | cloud-status | feedback <requestId> [generation] | status <requestId> | flows | usage [flowId] | billing | recurring list|claim|finish|runs | review | local (JSON on stdin)');
477
483
  }
478
484
  }
479
485
  if (process.argv[1] && realpathSync(process.argv[1]) === realpathSync(fileURLToPath(import.meta.url))) { const { captureIdeResults } = await import('./ide-results.mjs');
package/ide-command.mjs CHANGED
@@ -48,6 +48,7 @@ export function sdkCommand(parsed, runtime) {
48
48
  if (simple.has(args[0]) && args.length === 1 && parsed.input) return { args, input: parsed.input };
49
49
  if (['status', 'feedback'].includes(args[0]) && /^[a-f0-9]{64}$/u.test(args[1] ?? '') && [2,3].includes(args.length)) return { args };
50
50
  if (['flows', 'install', 'billing'].includes(args[0]) && args.length === 1 && !parsed.input) return { args };
51
+ if (args[0] === 'install' && args.length === 3 && args[1] === '--host' && args[2] === 'codex' && !parsed.input) return { args };
51
52
  if (args[0] === 'recurring' && ['list', 'claim'].includes(args[1] ?? '') && args.length === 2 && !parsed.input) return { args };
52
53
  if (args[0] === 'recurring' && ['finish', 'runs'].includes(args[1] ?? '') && args.length === 3 && (!parsed.input || args[1] === 'finish')) return { args, ...(parsed.input ? { input: parsed.input } : {}) };
53
54
  if (args[0] === 'usage' && args.length <= 2 && !parsed.input
@@ -61,8 +62,9 @@ export function installerCommand(parsed) {
61
62
  if (!parsed || parsed.input || !['npx', 'npx.cmd'].includes(parsed.words[0])) return null;
62
63
  const words = [...parsed.words]; words.shift();
63
64
  while (['--yes', '--prefer-online', '--registry=https://registry.npmjs.org'].includes(words[0])) words.shift();
64
- if (words.length !== 2 || !/^@entrorove\/skill(?:@(?:latest|\d+\.\d+\.\d+))?$/u.test(words[0]) || words[1] !== 'install') return null;
65
- return `npx --yes --prefer-online --registry=https://registry.npmjs.org ${words[0]} install`;
65
+ if (![2, 4].includes(words.length) || !/^@entrorove\/skill(?:@(?:latest|\d+\.\d+\.\d+))?$/u.test(words[0]) || words[1] !== 'install'
66
+ || words.length === 4 && (words[2] !== '--host' || words[3] !== 'codex')) return null;
67
+ return `npx --yes --prefer-online --registry=https://registry.npmjs.org ${words[0]} install${words.length === 4 ? ' --host codex' : ''}`;
66
68
  }
67
69
 
68
70
  export function readCommand(parsed) {
package/ide-hook.mjs CHANGED
@@ -101,7 +101,7 @@ function collectResult(state, values, local) {
101
101
  }
102
102
  async function beforeTool(event, state, home) {
103
103
  if (state.phase === 'user-exited') return {};
104
- const unlocked = await readBreakGlass(home);
104
+ const unlocked = await readBreakGlass(home, state.sessionId);
105
105
  const command = event.tool_input?.command ?? event.tool_input?.cmd, parsed = literalCommand(command);
106
106
  const isShell = ['Bash', 'exec_command', 'shell_command'].includes(event.tool_name);
107
107
  const installer = isShell && installerCommand(parsed);
@@ -230,7 +230,7 @@ export async function runIdeHook(event, { home } = {}) {
230
230
  if (resumed) messages.push(resumed);
231
231
  return { ...output(name, { additionalContext: ideContext(state) }), ...(messages.length ? { systemMessage: messages.join('\n') } : {}) };
232
232
  }
233
- if (name==='Stop' && state.active && !(await readBreakGlass(home))) {
233
+ if (name==='Stop' && state.active && !(await readBreakGlass(home, state.sessionId))) {
234
234
  const resumed = await advanceIdeQueue(state,home);
235
235
  if (resumed || unclassifiedIdeRequirements(state) || unfinishedIdeQueue(state) && !['blocked','needs-input','delivered'].includes(state.phase)
236
236
  || !['ended','blocked','needs-input','setup','delivered'].includes(state.phase)) return {decision:'block',reason:blockedReason(state, event, `${ideContext(state)}${ideQueueContext(state)}${resumed}原任务或队列尚未取得结束回执;继续可运行工作,真实阻断必须明确报告。`)};
package/ide-session.mjs CHANGED
@@ -6,7 +6,7 @@ import path from 'node:path';
6
6
  import { credentialDirectory, readCredential } from './local-credential.mjs';
7
7
  import { withStateLock } from './local-state-lock.mjs';
8
8
  import { requestTransport, parseTransportResponse } from './remote-transport.mjs';
9
- import { breakGlassPath, breakGlassSchema, writeBreakGlass as writeOfflineBreakGlass } from './break-glass.mjs';
9
+ import { breakGlassPath, breakGlassSchema, breakGlassSessionDigest, writeBreakGlass as writeOfflineBreakGlass } from './break-glass.mjs';
10
10
 
11
11
  export const ideDigest = value => createHash('sha256').update(typeof value === 'string' ? value : JSON.stringify(value)).digest('hex');
12
12
  export const ideError = (code, message) => Object.assign(new Error(message), { code });
@@ -35,10 +35,12 @@ export async function writeIdeJson(file, value) {
35
35
  try { await fs.rename(stage, file); } finally { await fs.rm(stage, { force: true }); }
36
36
  }
37
37
  export const writeBreakGlass = writeOfflineBreakGlass;
38
- export async function readBreakGlass(home = os.homedir()) {
38
+ export async function readBreakGlass(home = os.homedir(), sessionId) {
39
+ if (!sessionId) return null;
39
40
  const directory = await ideDirectory(home, true);
40
41
  const value = await readIdeJson(breakGlassPath(home));
41
42
  if (!value || value.schemaVersion !== breakGlassSchema || value.mode !== 'management-only'
43
+ || value.sessionDigest !== breakGlassSessionDigest(sessionId)
42
44
  || typeof value.expiresAt !== 'string' || !Number.isFinite(Date.parse(value.expiresAt))
43
45
  || Date.parse(value.expiresAt) <= Date.now()) return null;
44
46
  return value;
@@ -109,6 +111,22 @@ async function declaredHost(environment) {
109
111
  export async function assertIdeExecution(args, input, { home = os.homedir(), environment = process.env } = {}) {
110
112
  const { host, sessionId } = ideGuardBinding(environment);
111
113
  if (!host || host === 'Entrorove CLI') return;
114
+ const lifecycle = args[0] === 'local' ? args[1] : args[0] === 'invoke' ? input?.operation : null;
115
+ if (['flow-cancel', 'flow-abandon'].includes(lifecycle) && await readBreakGlass(home, sessionId)) {
116
+ const flowId = args[0] === 'invoke' ? input?.input?.flowId : input?.flowId;
117
+ const state = await readIdeJson(path.join(await ideDirectory(home), `session-${ideDigest(sessionId)}.json`));
118
+ if (state?.flowId && state.flowId !== flowId) throw ideError('ENTROROVE_IDE_RECOVERY_SCOPE', 'Recovery must settle the flow bound to this IDE session');
119
+ if (args[0] === 'local') {
120
+ const root = input?.repositoryRoot ?? args[2];
121
+ if (state?.repositoryRoot && root && await fs.realpath(root) !== state.repositoryRoot) {
122
+ throw ideError('ENTROROVE_IDE_RECOVERY_SCOPE', 'Recovery cannot switch the IDE session repository');
123
+ }
124
+ if (input?.home && await fs.realpath(input.home) !== await fs.realpath(home)) {
125
+ throw ideError('ENTROROVE_IDE_RECOVERY_SCOPE', 'Recovery cannot switch the IDE session home');
126
+ }
127
+ }
128
+ return;
129
+ }
112
130
  const declared = verifiedIdeGuards[host] ? null : await declaredHost(environment);
113
131
  if (!verifiedIdeGuards[host] && declared !== host || !sessionId) {
114
132
  throw ideError('ENTROROVE_IDE_GUARD_UNSUPPORTED', `${host} has no verified tool guard adapter. Execution is blocked; never continue with another executor.`);
@@ -124,3 +142,26 @@ export async function assertIdeExecution(args, input, { home = os.homedir(), env
124
142
  approvedResultContext = { home, sessionId: state.sessionId, repositoryRoot: state.repositoryRoot,
125
143
  accountDigest: state.accountDigest, toolUseId: approved.toolUseId, inputDigest: expected };
126
144
  }
145
+
146
+ export async function settleBreakGlassSession(args, input, result, { home = os.homedir(), environment = process.env } = {}) {
147
+ const operation = args[0] === 'local' ? args[1] : args[0] === 'invoke' ? input?.operation : null;
148
+ if (!['flow-cancel', 'flow-abandon'].includes(operation)) return false;
149
+ const value = result?.output ?? result;
150
+ const flowId = args[0] === 'invoke' ? input?.input?.flowId : input?.flowId;
151
+ if (value?.operation !== operation || value.flowId !== flowId || !['succeeded', 'reported'].includes(value.status)
152
+ || operation === 'flow-abandon' && value.unbound !== true) return false;
153
+ const { host, sessionId } = ideGuardBinding(environment);
154
+ if (!host || host === 'Entrorove CLI' || !await readBreakGlass(home, sessionId)) return false;
155
+ const file = path.join(await ideDirectory(home), `session-${ideDigest(sessionId)}.json`);
156
+ const current = await readIdeJson(file);
157
+ if (!current) return false;
158
+ return ideSession({ session_id: sessionId, cwd: current.repositoryRoot }, state => {
159
+ if (state.flowId !== flowId) throw ideError('ENTROROVE_IDE_RECOVERY_SCOPE', 'Recovered flow no longer matches the IDE session');
160
+ if (operation === 'flow-abandon') {
161
+ for (const key of ['flowId', 'taskId', 'taskNumber', 'requestId', 'remoteRequestId', 'nextStep', 'pendingCall', 'readCalls']) delete state[key];
162
+ state.feedbackAcknowledged = false;
163
+ if (state.phase !== 'user-exited') state.phase = 'entry';
164
+ } else state.phase = 'ended';
165
+ return true;
166
+ }, home);
167
+ }
@@ -39,12 +39,14 @@ async function removeDuplicate(directory, target, root, isManaged) {
39
39
  return 'removed';
40
40
  }
41
41
 
42
- export async function cleanManagedDuplicates({ target, cwd, home, isManaged, includeProject = true }) {
42
+ export async function cleanManagedDuplicates({ target, cwd, home, isManaged, includeProject = true, hostIds = null }) {
43
43
  const root = includeProject ? gitRoot(cwd) : null, homeRoot = gitRoot(home);
44
+ const includeClaude = hostIds === null || hostIds.includes('claude');
44
45
  const candidates = [
45
- ...(root ? ['.codex/skills/entrorove', '.agents/skills/entrorove', '.claude/skills/entrorove']
46
+ ...(root ? ['.codex/skills/entrorove', '.agents/skills/entrorove',
47
+ ...(includeClaude ? ['.claude/skills/entrorove'] : [])]
46
48
  .map(relative => ({ directory: path.join(root, relative), label: relative, root })) : []),
47
- ...['.agents/skills/entrorove', '.claude/skills/entrorove']
49
+ ...['.agents/skills/entrorove', ...(includeClaude ? ['.claude/skills/entrorove'] : [])]
48
50
  .map(relative => ({ directory: path.join(home, relative), label: `~/${relative}`, root: homeRoot })),
49
51
  ];
50
52
  const removed = [], preserved = [], seen = new Set([target]);
@@ -4,11 +4,6 @@ import { inspectLocalChainResult } from './local-chain-plan.mjs';
4
4
 
5
5
  const STAGES = Object.freeze({ 'workflow-gate': 'aimlock', 'workflow-contract': 'aimlock',
6
6
  'workflow-plan': 'blueprint', 'workflow-dispatch': 'swarm', 'workflow-verdict': 'validator' });
7
- function nativeSchema(runtime, operation) {
8
- const schemas = runtime.operationSchemas;
9
- const selected = Array.isArray(schemas) ? schemas.find(value => value.operation === operation) : schemas?.[operation];
10
- return selected?.inputSchema ?? selected?.input ?? selected;
11
- }
12
7
  export async function captureChainCloud(plan, client) {
13
8
  if (!plan.skills.length) return null;
14
9
  const output = await client.invoke('capabilities', {}), capabilities = output.capabilities;
@@ -37,10 +32,16 @@ export function checkChainSkillDispatch(original, state, resumed, input) {
37
32
  }
38
33
  const slug = STAGES[original.operation] ?? resumed.route?.slug;
39
34
  if (slug !== original.skillId) fail('ENTROROVE_CHAIN_SKILL_BINDING', 'declared skill differs from the original native workflow stage');
40
- const runtime = state.cloud.runtimes.find(value => value.slug === original.skillId);
41
- const schema = state.cloud.workflowOperations?.operationSchemas?.[original.operation] ?? nativeSchema(runtime, original.operation);
35
+ // `continue` is a workflow operation. A native skill operation with the
36
+ // same name can have a different request body (for example, the workflow
37
+ // reads its persisted interaction while the native runtime requires one).
38
+ const schema = state.cloud.workflowOperations?.operationSchemas?.[original.operation];
42
39
  if (schema) {
43
- const validate = new Ajv({ strict: true, strictRequired: false, allErrors: true }).compile(schema);
40
+ // Pinned native schemas include legacy `optional` annotations and nested
41
+ // object branches without an explicit `type`. Keep validation of the
42
+ // actual JSON Schema constraints while accepting those published forms.
43
+ const validate = new Ajv({ strict: true, strictRequired: false, strictTypes: false, allErrors: true })
44
+ .addKeyword({ keyword: 'optional', schemaType: 'boolean', valid: true }).compile(schema);
44
45
  if (!validate(input)) fail('ENTROROVE_CHAIN_SKILL_INPUT', 'bound input violates its fixed original operation schema');
45
46
  }
46
47
  // Native operations without public nested schemas remain server-validated on
package/local-cli.mjs CHANGED
@@ -25,7 +25,7 @@ import { ARCHGUARD_LOCAL_OPERATIONS } from './local-archguard-operations.mjs';
25
25
  import { LOCAL_BUDGET_HANDOFF_OPERATIONS } from './local-budget-handoff.mjs';
26
26
  import { LOCAL_CHAIN_OPERATIONS } from './local-chain-operations.mjs';
27
27
  import { DISCIPLINE_LOCAL_OPERATIONS } from './local-discipline-operations.mjs';
28
- import { assertIdeExecution } from './ide-session.mjs';
28
+ import { assertIdeExecution, settleBreakGlassSession } from './ide-session.mjs';
29
29
  import { runManagedWorkflowLocalStep } from './workflow-local-client.mjs';
30
30
  import { localErrorEnvelope } from './local-error.mjs';
31
31
 
@@ -101,6 +101,7 @@ export async function runLocal(args = [], dependencies) {
101
101
  const validated = await checkedLocalInput(operation, input);
102
102
  const supplied = operation === 'workflow-run-step' || Object.hasOwn(LOCAL_CHAIN_OPERATIONS, operation) ? dependencies : undefined;
103
103
  const output = await withLocalInvocation(operation, validated, () => withBudgetReadContext(validated, () => handler(validated, supplied)));
104
+ await settleBreakGlassSession(['local', ...args], validated, output);
104
105
  process.stdout.write(`${JSON.stringify(output)}\n`);
105
106
  if (['pending', 'blocked', 'failed', 'incomplete'].includes(output?.status)) process.exitCode = 2;
106
107
  } catch (cause) {
@@ -115,5 +116,7 @@ export async function runLocalInput(operation, input, dependencies) {
115
116
  if (!handler) throw new Error(`Unsupported local operation: ${operation}`);
116
117
  const validated = await checkedLocalInput(operation, input);
117
118
  const supplied = operation === 'workflow-run-step' || Object.hasOwn(LOCAL_CHAIN_OPERATIONS, operation) ? dependencies : undefined;
118
- return withLocalInvocation(operation, validated, () => withBudgetReadContext(validated, () => handler(validated, supplied)));
119
+ const output = await withLocalInvocation(operation, validated, () => withBudgetReadContext(validated, () => handler(validated, supplied)));
120
+ await settleBreakGlassSession(['local', operation], validated, output);
121
+ return output;
119
122
  }
@@ -96,8 +96,8 @@ async function performRole16(role, prepared) {
96
96
  verifiedAt: status === 'verified' ? new Date(now).toISOString() : null };
97
97
  return { execution, result, response };
98
98
  }
99
- function finished16(input, prepared, record) {
100
- const now = Date.now(), roles = Object.fromEntries(FINAL_GATE16_ROLES.map(role => [role, record.roles[role].response]));
99
+ export function finished16(input, prepared, record, now = Date.now()) {
100
+ const roles = Object.fromEntries(FINAL_GATE16_ROLES.map(role => [role, record.roles[role].response]));
101
101
  let status = FINAL_GATE16_ROLES.every(role => roles[role].status === 'verified') ? 'verified'
102
102
  : FINAL_GATE16_ROLES.some(role => roles[role].status === 'blocked') ? 'blocked' : 'incomplete';
103
103
  if (status === 'verified') for (const role of FINAL_GATE16_ROLES) {
package/manifest.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "schemaVersion": "entrorove.skill-manifest/1.0",
3
3
  "name": "entrorove",
4
- "version": "0.1.43",
4
+ "version": "0.1.48",
5
5
  "endpoint": "https://entrorove.com",
6
6
  "requestSchemaVersion": "entrorove.skill.request/2.0",
7
7
  "supportedRequestSchemaVersions": [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@entrorove/skill",
3
- "version": "0.1.43",
3
+ "version": "0.1.48",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "entrorove": "cli.mjs"
@@ -1,14 +1,14 @@
1
1
  {
2
2
  "schemaVersion": "entrorove.release-state/1.0",
3
- "version": "0.1.43",
4
- "digest": "4def2226e316c684fd009096f1bcafeec0c3519a3d4532cebe203ef095834467",
3
+ "version": "0.1.48",
4
+ "digest": "6f79c0e2c12d4e8766801bf117bcf73070156ba89fb87e4b1137c9706b8f7df3",
5
5
  "files": {
6
6
  "ide-code-mode.mjs": "f3f53357ba09727b3b597851976a24de93287c0faca5052448277d0904bb61f7",
7
- "ide-command.mjs": "158d8f26c1dc28a04284e429150a2060d7278661f656a75e7e9f7600f536b7bf",
8
- "ide-hook.mjs": "a2f151a6d1651872b0160487493d3ecedf01e30301b7af3d336201e7f6135e32",
7
+ "ide-command.mjs": "dcd4b48305cc4f106e5e4509e794e4b46ef02de29afa9cccbbc2774308c81ff3",
8
+ "ide-hook.mjs": "2f00e56de1e1149f117714d5dd334196f1a3f206c3bfab541393bc0f625e0108",
9
9
  "ide-install.mjs": "10247efadcfeef5da001022d12080bd0f789e67fdf7862bcc65db73dda3bbe57",
10
- "ide-session.mjs": "ce3ee463804faf4c557d82ff221b0bf7ddb6f6ab7257ce7e9e0af8fbc15d57b8",
11
- "break-glass.mjs": "2ad6f084c788d63fec11e64412864ac715bcb1a282dd2d8f03dc14e89e82135e",
10
+ "ide-session.mjs": "dc256e77b03303932d354768c0e79384340a3137e7421b4ae9c869f7e58e1a3a",
11
+ "break-glass.mjs": "6ec9f97c5d68bb33121b2374c06b9d4c8eb3419b52d00849fec7124bc63e4582",
12
12
  "ide-results.mjs": "dfe0d3f773c65a9fc21056538d7005012c982ed4509bc0410d7f62813d0863fd",
13
13
  "ide-queue.mjs": "806e716c775fd0079417027b135977c8516f47dddb0bc5ca19e32a355dab0e9e",
14
14
  "local-discipline-actions.mjs": "2379c08d0f49c44068f8239db7e514d75885f4606b1b88a7a36e4ff19167caca",
@@ -29,10 +29,10 @@
29
29
  "runtime/validator-runtime-v7.0.44.d.mts": "7797edbfe2f7de24da370204dce4dfd22822a1bd80df93514fff900b98afe36f",
30
30
  "snapshot-retention.mjs": "4f8919fe8ec49fb4c23e080a8ad2fa3a9282bca3fffe7cf55c64e89ef316697e",
31
31
  "snapshot-cleanup-journal.mjs": "76a9684cc95b4eb076e1298d78a9dff37948b00d9607b8f546c599072d3ed331",
32
- "IDE-EXECUTION.md": "64f48dd283f8b7ad76bd33b542baebc3ae4380315c4b7158974927fd4f7a4c17",
32
+ "IDE-EXECUTION.md": "6c8b9abe17cc8f01aa3da2ab19ed34b2b9be3e3b38c1739fa6a7d763622c4864",
33
33
  "REUSE-DISCIPLINE.md": "295ecd41c931b00c21d009ab65ac160f0e2ab81b4000ed498d2e8e97c2dc6c63",
34
34
  "reuse-discipline-release.mjs": "20ded9c21bd71b8779c454f5eba4c7c0a943ef99d94117b9bbef891d2ab6e3de",
35
- "local-final-gate16.mjs": "b30caab08441449ad556a6e9a3cefbf91190f0b3f7370f46d2a21156375aa8e3",
35
+ "local-final-gate16.mjs": "083aec470670e6cdb7ad2ab27b7a51b53a9ec3a8b4121cb9cbfa17d9940892c2",
36
36
  "local-final-gate16-checks.mjs": "1dd30e9666d458d8b12ca6e4975f6f0ac8450c3ea9cbecc7401ae037f66b589a",
37
37
  "local-final-gate16-worker.mjs": "344d64b6c1c5552b96960e0d8514a876a3dbfa9762ab9eb749038e8351727121",
38
38
  "local-final-gate16-plan.mjs": "9d34c51fa239376e552abc312409a7f3258519efe0b29d88a5054680cdda483c",
@@ -73,18 +73,18 @@
73
73
  "local-autocoord17-waits.mjs": "2b0a713987822548a94e1ed3a9a891532db45383797dfc5d5bc8d1e35593cf25",
74
74
  "local-validator-isolation17.mjs": "48e94604e28b8fddd567dfb35ff60b73fd094a5803bd49f076388b7f310f92aa",
75
75
  "local-calctool-composition17.mjs": "ac89fcb41ab0d0d9b47c1b5696945281b0894eb064820775e36a011c76314e6a",
76
- "cli.mjs": "14e27d25acf2838f3eddd24a21ec438baa82650ac2235fb471f15718e996109f",
77
- "install-cleanup.mjs": "72bb6bd55382449a986828cd1ae3e98e5fd0bf373f89bbdcc039023ae72a2337",
76
+ "cli.mjs": "2f1fa353a80000ed26061feeb44a6a4ad12e32a2f49c84bfbb6c54f89f519b7b",
77
+ "install-cleanup.mjs": "8a8e1a7dfe37427f86cd4d6e317890d0829894a9949d4216674a716b36edbc54",
78
78
  "review-client.mjs": "3491eef68ed533174b31db3c53febb6519b8f088f71a9964644714a9a4c03c77",
79
79
  "version.mjs": "1f392147d72c40af4b03b307ea607075fb815af5174fa07e254b95fe12dbf3e7",
80
- "SKILL.md": "5531f764b82b3c2fe89592d291d05f508711b1498a65c75219c2b4396b532467",
80
+ "SKILL.md": "0d581e76d8c959ae04e091e24df5580e56d9ec9b6c1c6fa25a531a21e8b88ea4",
81
81
  "CAPABILITY-MAP.md": "a0f0468a098725ced52a96dd8e22bfc7d1ce7681a8446ebf7dae3696c76c3385",
82
82
  "AGREEMENT.md": "f93a68e92c457200dd024610e65f74e478b8f60860f951da818b70561f1b9bf8",
83
83
  "DELIVERY-BOUNDARIES.md": "d1bb2895e23fc14bf1c76707bd1f6f03b39695501a78a0b7289dc37b099889dc",
84
84
  "package.json": "8be823b5756da9b393af966a4152bee301cb49f988823aaf757e8c51bf867085",
85
85
  "manifest.json": "ab06e307795bc9da5add926752b6c4dbdaf18625090261a035aa356a679c504f",
86
- "release.mjs": "9b71dc6dbda1e8db1097f0b14dbc1d66b3968ed241813f32851ceef54facec77",
87
- "local-cli.mjs": "aa4c217c5029408020f76d6c0c5dafc9a0f51216085b2624f3b6aca3cffbe555",
86
+ "release.mjs": "34a3da868e8b5b586b318f2e8275f2f238d48a20afceb7d51c0f783a120f6973",
87
+ "local-cli.mjs": "11461e4cf4f6a8612ff275bf8296ec57ff22281156cb2c72834d3c764ba422a1",
88
88
  "local-state-lock.mjs": "1dbf64264642a0851faf043e07ee9f66d2ad0508e9f799b0626246e42fae893e",
89
89
  "local-execution.mjs": "46fddd78cd954fb080c660affe90cdaa716695580427336acb820089441a8025",
90
90
  "local-integrity.mjs": "e6d6eace80c610f3c7299d4f8644b0dc023aa9e3d8d64b26535ea7fbced248f1",
@@ -95,7 +95,7 @@
95
95
  "blueprint-criterion-wire.mjs": "fb6b7dcce0b97a699e3e8968372767a3fddd83fedc750998573f789177243072",
96
96
  "blueprint-criterion-runner.mjs": "761c0e670b4b20c6bed53e5ca5d0178759505c3977ea2ed1b7223383c534bc4c",
97
97
  "local-chain-bindings.mjs": "b8eff999ccd17b53c614a3c9f5376da78fcaa8632bb3e07bc1adcceda2e75f6c",
98
- "local-chain-broker.mjs": "0f43ae3d5e713a344d94c3f67891b0d5f6ed0b70a8d237a54d06c0fa36e4c6d8",
98
+ "local-chain-broker.mjs": "d9a63f2c9eec561853a84ab5fdb7d6794f256582126017c54e5d21d0d370b015",
99
99
  "local-chain-contracts.mjs": "d4d2be4e5c6980f2dda8ff598c7333a75e0bbf2f656aa7fe327af7c9fe73da07",
100
100
  "local-chain-controller.mjs": "dd80d8001a11d8433b615a017a34c30dd130ff5cd9c5b58731314a8f93c9571b",
101
101
  "local-chain-human.mjs": "9d5dbaef13ebbbe0cfdd1146cd9c48cefcca30f33ccc0b1566e67cbd730d3499",
@@ -152,7 +152,7 @@
152
152
  "local-scope-state.mjs": "8b600a91fc0068d7a8c0bb752ddba7ed30ada93daafb118d6cba2368fd94cb27",
153
153
  "local-scope-workspaces.mjs": "a44c34537e5ed2dcaf193a985e5bc05957989fbb160a650f9e37ba7d66eb2de8",
154
154
  "local-task-routing.mjs": "6e2e506b2fb0e2e8d8c5147d7df32849b57a4e94a23634f01a355d45735858e4",
155
- "remote-client.mjs": "8c2450613480d0bdb39e942fb1dea14d74d5ffadd3c72809c74aa5fbc3c8d773",
155
+ "remote-client.mjs": "bfb58132759a4312adc8ab33115d807c5a8d90c955aca3678e361ab9cefa84bd",
156
156
  "cloud-task-client.mjs": "11acb1a033c94cdfbdfb177a3402ad97f6a5226f577138c8cf6395dc3ebb1a39",
157
157
  "scope-runner-client.mjs": "646ab4f80d4a8209e922fd45f40efd968f0744d9dfc03188b222c1a2e11e83b8",
158
158
  "native-client.mjs": "6f5575909bae9daba643287f876ffa0f9ff1c53a367b32a40145d740456d6605",
package/release.mjs CHANGED
@@ -278,10 +278,7 @@ function releaseNote(changed) {
278
278
  const distinct = distinctReleaseLabels(changed, releaseLabels())
279
279
  return { zh: `更新了${distinct.map(label => label.zh).join('、')}。`, en: `Updated ${distinct.map(label => label.en).join(', ')}.` }
280
280
  }
281
- function releaseLabels() {
282
- return withFinalGate16ReleaseLabels({ ...reuseDisciplineReleaseLabels, ...calctoolReleaseLabels, ...mutationReleaseLabels, ...swarmReleaseLabels, ...coordination17ReleaseLabels, ...nativeReadReleaseLabels, ...feedbackReleaseLabels, ...invocationReleaseLabels, ...chainReleaseLabels, ...taskEntryReleaseLabels,
283
- 'blueprint-criterion-wire.mjs': { zh: '蓝图验收标准执行授权', en: 'Blueprint criterion execution authority' },
284
- 'blueprint-criterion-runner.mjs': { zh: '蓝图验收标准实际执行器', en: 'Blueprint criterion process runner' },
281
+ const archguardReleaseLabels = {
285
282
  'local-archguard-ast.mjs': { zh: 'Archguard 架构合同与执行守卫', en: 'Archguard architecture contract and execution guard' },
286
283
  'local-archguard-checkpoint.mjs': { zh: 'Archguard 架构合同与执行守卫', en: 'Archguard architecture contract and execution guard' },
287
284
  'local-archguard-components.mjs': { zh: 'Archguard 架构合同与执行守卫', en: 'Archguard architecture contract and execution guard' },
@@ -298,6 +295,11 @@ function releaseLabels() {
298
295
  'local-archguard.mjs': { zh: 'Archguard 架构合同与执行守卫', en: 'Archguard architecture contract and execution guard' },
299
296
  'archguard-runtime-build.mjs': { zh: 'Archguard 架构合同与执行守卫', en: 'Archguard architecture contract and execution guard' },
300
297
  'runtime/archguard-runtime-v7.0.44.mjs': { zh: 'Archguard 架构合同与执行守卫', en: 'Archguard architecture contract and execution guard' },
298
+ }
299
+ function releaseLabels() {
300
+ return withFinalGate16ReleaseLabels({ ...reuseDisciplineReleaseLabels, ...calctoolReleaseLabels, ...mutationReleaseLabels, ...swarmReleaseLabels, ...coordination17ReleaseLabels, ...nativeReadReleaseLabels, ...feedbackReleaseLabels, ...invocationReleaseLabels, ...chainReleaseLabels, ...taskEntryReleaseLabels, ...archguardReleaseLabels,
301
+ 'blueprint-criterion-wire.mjs': { zh: '蓝图验收标准执行授权', en: 'Blueprint criterion execution authority' },
302
+ 'blueprint-criterion-runner.mjs': { zh: '蓝图验收标准实际执行器', en: 'Blueprint criterion process runner' },
301
303
  'cli.mjs': { zh: '技能运行和受管安装', en: 'skill runtime and managed installation' },
302
304
  'install-cleanup.mjs': { zh: '旧版受管安装清理', en: 'managed installation cleanup' },
303
305
  'version.mjs': { zh: '版本安全检查', en: 'version safety checks' },
package/releases.json CHANGED
@@ -1,4 +1,34 @@
1
1
  [
2
+ {
3
+ "version": "0.1.48",
4
+ "date": "2026-10-16",
5
+ "zh": "新增标准命令 `install --host codex`,仅配置 Codex 并保留 Claude 的配置与技能副本;后续自动更新沿用已配置的宿主范围。安装命令守卫和旧版清理也按目标宿主限界。",
6
+ "en": "Added the standard `install --host codex` command to configure Codex while preserving Claude configuration and skill copies. Later automatic updates retain the already configured host scope; installer command guards and obsolete-install cleanup honor the selected host."
7
+ },
8
+ {
9
+ "version": "0.1.47",
10
+ "date": "2026-10-16",
11
+ "zh": "修复失败 flow 的取消、放弃和会话解绑;离线 unlock 仅恢复当前会话并保留任务队列。放行授权根或依赖目录内的只读 tsconfig 引用,越界错误说明原路径、解析结果和原因。升级时清理未改动的旧受管安装,保留用户编辑及其他任务;IDE 钩子仅对已绑定的会话和仓库生效。",
12
+ "en": "Fixed cancellation, abandonment, and session unbinding for failed flows; offline unlock restores only the current session and preserves its queue. Read-only tsconfig references inside the authorized root or dependency directories are accepted, while escape errors identify the input path, resolved path, and reason. Upgrades remove pristine obsolete managed installs while preserving user edits and unrelated tasks; IDE hooks apply only to a bound session and repository."
13
+ },
14
+ {
15
+ "version": "0.1.46",
16
+ "date": "2026-10-16",
17
+ "zh": "更新了Codex 后续轮次工具门禁、IDE 执行与复用纪律文档、技能运行和受管安装、IDE 工作流程说明。",
18
+ "en": "Updated Codex tool guards across turns, IDE execution and reuse discipline documentation, skill runtime and managed installation, IDE workflow instructions."
19
+ },
20
+ {
21
+ "version": "0.1.45",
22
+ "date": "2026-10-16",
23
+ "zh": "更新了原链同流程技能调用。",
24
+ "en": "Updated original-chain skill calls on the same workflow."
25
+ },
26
+ {
27
+ "version": "0.1.44",
28
+ "date": "2026-10-16",
29
+ "zh": "更新了Calctool 审计测试运维真实检查与独立最终闸门、自动版本与更新日志。",
30
+ "en": "Updated Calctool audit, test, operations checks and independent final gate, automatic versioning and changelog."
31
+ },
2
32
  {
3
33
  "version": "0.1.43",
4
34
  "date": "2026-10-16",
package/remote-client.mjs CHANGED
@@ -85,6 +85,7 @@ export async function invokeRemote(data, dependencies) {
85
85
  process.stdout.write(JSON.stringify(presentInvocationNotices(result,dependencies.onIdeEvent,{home})) + '\n');
86
86
  if (!response.ok || result.output.status === 'blocked' || result.output.status === 'failed'
87
87
  || result.output.status === 'incomplete') process.exitCode = 2;
88
+ return result;
88
89
  }
89
90
 
90
91
  export async function invokeRemoteResult(data, dependencies) {