@entrorove/skill 0.1.60 → 0.1.61

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,10 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.61 — 2026-10-18
4
+
5
+ - 钩子改为可选:默认安装关闭钩子,IDE 可直接调用 SDK;只有明确选用技能的原会话与仓库启用拦截,普通查询、旧激活状态及无法归属的事件不再接管其他任务。保留账号、写入范围、签名和真实本地验收校验。
6
+ - Make hooks optional: default installs disable hooks and IDEs can call the SDK directly. Interception requires explicit selection of the original session and repository; discovery, legacy activation and unidentified events cannot govern unrelated work. Preserve account, mutation scope, signatures and actual local acceptance checks.
7
+
3
8
  ## 0.1.60 — 2026-10-17
4
9
 
5
10
  - 修复范围补正误用独立交接的完整依赖校验,接收原账号签名器的真实受限扫描并保留未解析依赖、原审批与预算;新增当前 IDE 会话离线 exit,识别明确停用决定并保持退出状态,不再要求重建流程。
package/IDE-EXECUTION.md CHANGED
@@ -1,6 +1,11 @@
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.
1
+ Install or update the managed copy with `npx @entrorove/skill install --host codex`. The default installs the skill with hooks disabled; SDK operations work directly in Codex, Claude Code or other IDEs without a hook approval. An explicit default reinstall removes only the recorded Codex hooks. It leaves Claude Code settings and skill copies alone, preserves hand-edited files and cannot downgrade a newer managed skill. Ordinary commands never install the skill or restore removed callbacks.
2
2
 
3
- Host hook events, including multimodal `PostToolUse` screenshot results, accept at most 32 MiB (33,554,432 UTF-8 bytes). The complete bounded event is decoded once and follows the original session, tool-call and signature checks; image results do not grant permissions or erase a pending call. Oversized events return `ENTROROVE_IDE_INPUT_TOO_LARGE`; invalid UTF-8 or JSON objects return `ENTROROVE_IDE_INPUT_INVALID_JSON`. These fixed diagnostics do not include the event contents. Private IDE state JSON files retain their separate 1,000,000-byte limit. A hook error after a tool action does not prove the action was unexecuted: inspect the original result or status before considering another attempt.
3
+ Hooks are optional: use `install --host codex --hooks` only when the user requests interception. Registration alone is passive. An explicit prompt (`使用 entrorove` or `$entrorove`) selects that exact IDE session and physical repository. Discovery, status, SDK calls and legacy active bits never select it. Unselected callbacks create no session/queue state and cannot deny native tools or block Stop. `install --host codex --no-hooks` returns to direct SDK mode. A selected callback adds `ENTROROVE_HOOK_MODE=required` only to its rewritten SDK invocation; the CLI validates the original exact approval and account for that call. Do not set this internal marker in ordinary direct SDK commands.
4
+
5
+ Without hooks, explicitly call the returned SDK operations, preserve request IDs and signed receipts, and recover unknown outcomes through status. Account authentication, agreement, original scope, mutation passes and real test/acceptance checks are unchanged. SDK receipts cover only SDK operations; native IDE edits are not automatically intercepted, recorded or certified. Automatic prompt queue capture, callback synchronization and Stop continuation require an enabled, explicitly selected hook. Cloud sandbox preference is independent of hook mode.
6
+
7
+
8
+ Host hook events, including multimodal `PostToolUse` screenshot results, accept at most 32 MiB (33,554,432 UTF-8 bytes). The complete bounded event is decoded once and follows the original session, tool-call and signature checks; image results do not grant permissions or erase a pending call. Oversized events return `ENTROROVE_IDE_INPUT_TOO_LARGE`; invalid UTF-8 or JSON objects return `ENTROROVE_IDE_INPUT_INVALID_JSON`. These fixed diagnostics do not include the event contents. Malformed transport cannot identify a selected session, so it returns a nonblocking diagnostic rather than stopping unrelated work; errors inside a positively identified selected session still reject the affected call. Private IDE state JSON files retain their separate 1,000,000-byte limit. A hook error after a tool action does not prove the action was unexecuted: inspect the original result or status before considering another attempt.
4
9
 
5
10
  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.
6
11
 
package/SKILL.md CHANGED
@@ -1,17 +1,18 @@
1
1
  ---
2
2
  name: entrorove
3
- description: Execute an engineering task through Entrorove, preserving its original scope, approvals, cloud task identity and verifiable result across every IDE turn.
3
+ description: Execute a task selected for Entrorove through explicit SDK operations, preserving its scope, approvals and verifiable results. Hooks are optional; unrelated IDE work remains outside its governance.
4
4
  ---
5
5
 
6
6
  # entrorove · 熵巡
7
7
 
8
- Version: 0.1.60.
8
+ Version: 0.1.61.
9
9
 
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.
10
+ Entrorove applies only to the task for which it is selected; reading these instructions, discovering capabilities or installing the package never selects or governs another task. Hooks are optional. 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 --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.
14
+ 1. Install `npx --yes --prefer-online @entrorove/skill@latest install --host codex` only when the user chooses to install this skill. The default installs the skill with hooks disabled and removes its recorded Codex hooks on upgrade, preserving other host settings. SDK calls work directly in an IDE without a hook callback or supported hook adapter; retain the actual host identity. Missing credentials, failed discovery and the SDK/API's scope, approval and evidence checks still return explicit failures.
15
+ Only when the user requests automatic interception, use `install --host codex --hooks`. The hook stays passive until this session receives an explicit selection such as `使用 entrorove` or `$entrorove`. SDK discovery, installation, another session, another repository and old automatic activation records cannot select it. In a selected session, the hook binds each rewritten SDK call to its exact live approval. `install --host codex --no-hooks` disables only the recorded Codex hooks; ordinary calls never reinstall removed hooks. A disabled hook cannot be described as enforced. Do not ask the user to type a nonexistent `/hooks` command.
15
16
  `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
17
  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
18
  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.
@@ -26,7 +27,7 @@ Entrorove applies to the whole task once selected. Reading this document or call
26
27
  8. Read [DELIVERY-BOUNDARIES.md](DELIVERY-BOUNDARIES.md) before claiming a delivered stage or showing consumption. A delivered stage requires a clean boundary and uses `delivered-stage-N`. Blocked or unknown work can wait with recovery data intact; it is not a delivered stage. Display consumption only with a measured baseline.
27
28
  9. For independent verified completion, submit actual independent execution evidence and run server verification and verdict. For a signed member-local plan, finish all original local steps and requirement checkpoints, then call the returned `workflow-verdict`; only its signed final `localAcceptance` means local acceptance is complete. Describe it as local execution and tests accepted, with independent validation not performed. Never invoke formal V, merge, Swarm or handoff to relabel it. Display the confirmed evaluation acknowledgement and preserve `receiptId`, `requestId`, execution generation and evaluation digest. “已回传” requires `acknowledgement.stored: true`. Missing or failed feedback remains visible. Active, waiting and unknown tasks retain recovery data.
28
29
 
29
- The Codex guard preserves this binding over subsequent prompts and compaction and denies unsupported native mutations before execution. Hooks are host guardrails, not an operating-system security boundary; unsupported tool paths or a disabled/untrusted hook cannot be described as enforced. An explicit user decision to stop using the skill ends its governance and never becomes verified Entrorove delivery. Do not infer such a decision from quoted screenshots or a service failure. For that explicit decision, run `entrorove exit` in the affected IDE session. It works offline without a credential, agreement or hook callback, stops governance for that session and preserves its original flow, failed requests and queued work. It does not cancel the business goal, resume another flow or grant an Entrorove mutation pass. Do not substitute `flow-abandon` for user exit: abandonment releases a flow binding but leaves the skill selected.
30
+ When explicitly enabled and selected, the Codex guard preserves this binding over subsequent prompts and compaction and denies unsupported native mutations before execution. Without hooks, explicitly execute the returned SDK operations and retain their real request IDs and receipts; automatic prompt queue capture, callback recovery and Stop interception are unavailable. SDK evidence covers only operations performed through the SDK, not arbitrary IDE edits. Hooks are host guardrails, not an operating-system security boundary; unsupported tool paths or a disabled/untrusted hook cannot be described as enforced. An explicit user decision to stop using the skill ends its governance and never becomes verified Entrorove delivery. Do not infer such a decision from quoted screenshots or a service failure. For that explicit decision, run `entrorove exit` in the affected IDE session. It works offline without a credential, agreement or hook callback, stops governance for that session and preserves its original flow, failed requests and queued work. It does not cancel the business goal, resume another flow or grant an Entrorove mutation pass. Do not substitute `flow-abandon` for user exit: abandonment releases a flow binding but leaves the skill selected.
30
31
 
31
32
  After the same real blocked result occurs twice, diagnose it and use its supported recovery path. Do not send a third identical execution call. Read-only status and checkpoint acknowledgements do not count as execution progress. A blocked task may wait with its original flow, queue and recovery data intact; Stop must not force repeated automatic continuation of the same blocked turn.
32
33
 
package/cli.mjs CHANGED
@@ -22,6 +22,7 @@ import { cleanManagedDuplicates } from './install-cleanup.mjs';
22
22
  import { assertIdeExecution, ideGuardBinding, settleBreakGlassSession } from './ide-session.mjs';
23
23
  import { writeBreakGlass } from './break-glass.mjs';
24
24
  import { exitIdeSession } from './ide-exit.mjs';
25
+ import { installOptions } from './ide-command.mjs';
25
26
  const markerName = '.entrorove-managed.json';
26
27
  const installFiles = ['SKILL.md', 'CAPABILITY-MAP.md', 'AGREEMENT.md', 'manifest.json', 'IDE-EXECUTION.md', 'REUSE-DISCIPLINE.md', 'DELIVERY-BOUNDARIES.md'];
27
28
  const markerSchema = 'entrorove.managed-install/1.0';
@@ -247,8 +248,8 @@ async function updateGlobalInstall(target, bundle) {
247
248
  return true;
248
249
  }
249
250
 
250
- export async function install({ home = os.homedir(), cwd = process.cwd(), explicit = false, hostId } = {}) {
251
- const selected = hostId === undefined ? null : ideAdapters.find(adapter => adapter.id === hostId);
251
+ export async function install({ home = os.homedir(), cwd = process.cwd(), explicit = false, hostId, hooks = false } = {}) {
252
+ const selected = ideAdapters.find(adapter => adapter.id === (hostId ?? 'codex'));
252
253
  if (hostId !== undefined && hostId !== 'codex') throw new Error('Install host must be codex');
253
254
  const target = globalInstallPath(home);
254
255
  if (!explicit && !(await managedState(target))) return { installed: false, updated: false };
@@ -256,16 +257,19 @@ export async function install({ home = os.homedir(), cwd = process.cwd(), explic
256
257
  await fs.mkdir(parent, { recursive: true, mode: 0o700 });
257
258
  return withInstallLock(parent, async () => {
258
259
  const active = explicit ? null : await activeIdeAdapters({ home });
259
- if (!explicit && (!(await managedState(target)) || active.length === 0)) return { installed: false, updated: false };
260
+ if (!explicit && !(await managedState(target))) return { installed: false, updated: false };
260
261
  const bundle = await sourceBundle();
261
262
  const stale = await agreementStale(home);
262
263
  const updated = await updateGlobalInstall(target, bundle);
263
264
  const { removed, preserved } = await cleanManagedDuplicates({
264
265
  target, cwd, home, isManaged: managedState, includeProject: explicit,
265
- hostIds: selected ? [selected.id] : active?.map(adapter => adapter.id),
266
+ hostIds: explicit ? [selected.id] : active.map(adapter => adapter.id),
266
267
  });
267
- const ide = await installIdeHooks({ home, version: bundle.manifest.version,
268
- adapters: selected ? [selected] : active });
268
+ const targets = explicit ? hooks ? [selected] : [] : active;
269
+ const disabled = explicit && !hooks ? await uninstallIdeHooks({ home, adapters: [selected] }) : null;
270
+ const ide = targets.length ? await installIdeHooks({ home, version: bundle.manifest.version, adapters: targets })
271
+ : { host: selected.host, configured: false, enforcement: 'disabled', mode: 'explicit-sdk',
272
+ ...(disabled ?? {}), message: '技能已安装;钩子关闭,SDK 可直接调用。' };
269
273
  return { version: bundle.manifest.version, updated, removed, preserved, agreementStale: stale, ide };
270
274
  });
271
275
  }
@@ -436,8 +440,8 @@ async function main() {
436
440
  const result = await uninstall();
437
441
  process.stdout.write(`${JSON.stringify(result)}\n`);
438
442
  if (!result.uninstalled) process.exitCode = 2;
439
- } else if (command === 'install' && (args.length === 0 || args.length === 2 && args[0] === '--host')) {
440
- process.stdout.write(JSON.stringify(await install({ explicit: true, hostId: args[1] })) + '\n');
443
+ } else if (command === 'install' && installOptions([command, ...args])) {
444
+ process.stdout.write(JSON.stringify(await install({ explicit: true, ...installOptions([command, ...args]) })) + '\n');
441
445
  } else if (command === 'configure' && args.length === 0) {
442
446
  const data = await stdinJson();
443
447
  if (Object.keys(data).length !== 1 || typeof data.token !== 'string') throw new Error('Expected {"token":"..."} on stdin');
@@ -480,7 +484,7 @@ async function main() {
480
484
  } else if (command === 'local') {
481
485
  await runLocal(args);
482
486
  } else {
483
- throw new Error('Usage: entrorove exit | unlock | uninstall | install [--host codex] | configure | agreement | invoke | queue | native <domain> <operation> | notifications configure|status|test | scope-connect <repositoryRoot> | scope-amend <repositoryRoot> | mutation-connect <repositoryRoot> | mutation-settle <repositoryRoot> | workflow-observe-step <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)');
487
+ throw new Error('Usage: entrorove exit | unlock | uninstall | install [--host codex] [--hooks|--no-hooks] | configure | agreement | invoke | queue | native <domain> <operation> | notifications configure|status|test | scope-connect <repositoryRoot> | scope-amend <repositoryRoot> | mutation-connect <repositoryRoot> | mutation-settle <repositoryRoot> | workflow-observe-step <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)');
484
488
  }
485
489
  }
486
490
  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
@@ -55,6 +55,8 @@ function sdkWords(parsed) {
55
55
  seen.add(key);
56
56
  if (key === 'ENTROROVE_IDE') {
57
57
  if (!/^[\p{L}\p{N} .+_-]{2,48}$/u.test(value)) return null;
58
+ } else if (key === 'ENTROROVE_HOOK_MODE') {
59
+ if (value !== 'required') return null;
58
60
  } else if (!['ENTROROVE_SESSION', 'CODEX_THREAD_ID', 'CLAUDE_SESSION_ID'].includes(key)
59
61
  || !/^[A-Za-z0-9_-]{1,200}$/u.test(value)) return null;
60
62
  }
@@ -81,8 +83,8 @@ export function sdkCommand(parsed, runtime) {
81
83
  if (['status', 'feedback'].includes(args[0]) && /^[a-f0-9]{64}$/u.test(args[1] ?? '') && [2,3].includes(args.length)) return { args };
82
84
  if (args[0] === 'flows' && args.length === 1 && (!parsed.input || typeof parsed.input === 'object'
83
85
  && !Array.isArray(parsed.input) && Object.keys(parsed.input).length === 0)) return { args };
84
- if (['install', 'billing'].includes(args[0]) && args.length === 1 && !parsed.input) return { args };
85
- if (args[0] === 'install' && args.length === 3 && args[1] === '--host' && args[2] === 'codex' && !parsed.input) return { args };
86
+ if (args[0] === 'billing' && args.length === 1 && !parsed.input) return { args };
87
+ if (installOptions(args) && !parsed.input) return { args };
86
88
  if (args[0] === 'recurring' && ['list', 'claim'].includes(args[1] ?? '') && args.length === 2 && !parsed.input) return { args };
87
89
  if (args[0] === 'recurring' && ['finish', 'runs'].includes(args[1] ?? '') && args.length === 3 && (!parsed.input || args[1] === 'finish')) return { args, ...(parsed.input ? { input: parsed.input } : {}) };
88
90
  if (args[0] === 'usage' && args.length <= 2 && !parsed.input
@@ -96,9 +98,23 @@ export function installerCommand(parsed) {
96
98
  if (!parsed || parsed.input || !['npx', 'npx.cmd'].includes(parsed.words[0])) return null;
97
99
  const words = [...parsed.words]; words.shift();
98
100
  while (['--yes', '--prefer-online', '--registry=https://registry.npmjs.org'].includes(words[0])) words.shift();
99
- if (![2, 4].includes(words.length) || !/^@entrorove\/skill(?:@(?:latest|\d+\.\d+\.\d+))?$/u.test(words[0]) || words[1] !== 'install'
100
- || words.length === 4 && (words[2] !== '--host' || words[3] !== 'codex')) return null;
101
- return `npx --yes --prefer-online --registry=https://registry.npmjs.org ${words[0]} install${words.length === 4 ? ' --host codex' : ''}`;
101
+ if (!/^@entrorove\/skill(?:@(?:latest|\d+\.\d+\.\d+))?$/u.test(words[0])) return null;
102
+ const options = installOptions(words.slice(1));
103
+ if (!options) return null;
104
+ return `npx --yes --prefer-online --registry=https://registry.npmjs.org ${words[0]} install${options.hostId ? ' --host codex' : ''}${options.hooks ? ' --hooks' : ''}`;
105
+ }
106
+
107
+ export function installOptions(args) {
108
+ if (args[0] !== 'install') return null;
109
+ const options = { hooks: false }, seen = new Set();
110
+ for (let index = 1; index < args.length; index++) {
111
+ const key = args[index] === '--host' ? 'hostId' : ['--hooks', '--no-hooks'].includes(args[index]) ? 'hooks' : null;
112
+ if (!key || seen.has(key)) return null;
113
+ seen.add(key);
114
+ if (key === 'hostId') { if (args[++index] !== 'codex') return null; options.hostId = 'codex'; }
115
+ else options.hooks = args[index] === '--hooks';
116
+ }
117
+ return options;
102
118
  }
103
119
 
104
120
  export function readCommand(parsed) {
@@ -170,6 +186,6 @@ export function runtimeHashRead(parsed, runtime) {
170
186
 
171
187
  export function renderSdkCommand(call, runtime, sessionId, host = process.env.ENTROROVE_IDE ?? 'Codex') {
172
188
  const quote = value => `'${String(value).replace(/'/gu, "'\\''")}'`;
173
- const header = `ENTROROVE_IDE=${quote(host)} ENTROROVE_SESSION=${quote(sessionId)} CODEX_THREAD_ID=${quote(sessionId)} ${[runtime.node, runtime.cli, ...call.args].map(quote).join(' ')}`;
189
+ const header = `ENTROROVE_HOOK_MODE='required' ENTROROVE_IDE=${quote(host)} ENTROROVE_SESSION=${quote(sessionId)} CODEX_THREAD_ID=${quote(sessionId)} ${[runtime.node, runtime.cli, ...call.args].map(quote).join(' ')}`;
174
190
  return call.input ? `${header} <<'ENTROROVE_INPUT'\n${JSON.stringify(call.input)}\nENTROROVE_INPUT` : header;
175
191
  }
package/ide-hook.mjs CHANGED
@@ -14,6 +14,7 @@ import { verifyIdeLocalAcceptance } from './ide-local-acceptance.mjs';
14
14
  import { registerIdeRequirement, ideQueueContext, unclassifiedIdeRequirements,
15
15
  advanceIdeQueue, checkQueuedStart, unfinishedIdeQueue } from './ide-queue.mjs';
16
16
  import { explicitIdeExit, markIdeExited } from './ide-exit.mjs';
17
+ import { appliesIdeHook, hasIdeHookSelection, selectsIdeHook, ideUserPrompt as userPrompt } from './ide-selection.mjs';
17
18
 
18
19
  const output = (event, extra = {}) => ({ hookSpecificOutput: { hookEventName: event, ...extra } });
19
20
  const deny = reason => output('PreToolUse', { permissionDecision: 'deny', permissionDecisionReason: reason });
@@ -25,14 +26,6 @@ const inputErrors = {
25
26
  const publicId = value => typeof value === 'string' && /^[A-Za-z0-9_-]{1,200}$/u.test(value);
26
27
  const readTools = new Set(['wait', 'view_image', 'list_mcp_resources', 'list_mcp_resource_templates', 'read_mcp_resource', 'web_search', 'update_plan', 'request_user_input', 'request_user_input_async', 'get_goal']);
27
28
  const unlockCommand = 'entrorove unlock';
28
- const selectPrompt = /^(?:(?:请)?(?:使用|启用)\s+\$?entrorove|\$entrorove)(?:\s|[,。,::!!]|$)/iu;
29
- function userPrompt(event) {
30
- let prompt = (event.prompt ?? '').trim();
31
- for (const wrapper of ['**', '__', '`']) {
32
- if (prompt.startsWith(wrapper) && prompt.endsWith(wrapper)) prompt = prompt.slice(wrapper.length, -wrapper.length).trim();
33
- }
34
- return prompt;
35
- }
36
29
  const managementOperations = new Set(['capabilities', 'help', 'start', 'continue', 'task-describe', 'flow-cancel', 'flow-abandon', 'scope-connect', 'mutation-connect', 'mutation-settle', 'mutation-consumption-attest', 'mutation-delegate-prepare', 'status', 'feedback', 'cloud-status', 'cloud-watch', 'queue', 'unlock', 'usage', 'billing', 'recurring']);
37
30
  function operationOf(call) {
38
31
  if (!call) return null;
@@ -155,15 +148,12 @@ function allowInstaller(event, state, command) {
155
148
  return output('PreToolUse', { permissionDecision: 'allow', updatedInput: safeInput });
156
149
  }
157
150
  async function beforeTool(event, state, home) {
158
- if (state.phase === 'user-exited') return {};
151
+ if (!hasIdeHookSelection(state) || state.phase === 'user-exited') return {};
159
152
  const unlocked = await readBreakGlass(home, state.sessionId);
160
153
  const command = event.tool_input?.command ?? event.tool_input?.cmd, parsed = literalCommand(command);
161
154
  const isShell = ['Bash', 'exec_command', 'shell_command'].includes(event.tool_name);
162
155
  const installer = isShell && installerCommand(parsed);
163
156
  if (installer) return allowInstaller(event, state, installer);
164
- const namedSdk = parsed?.words.some(word => word === 'entrorove' || /^@entrorove\/skill(?:@[^ ]+)?$/u.test(word)
165
- || /(?:@entrorove\/skill|entrorove-skill)\/cli\.mjs$/u.test(word));
166
- if (!state.active && !namedSdk) return {};
167
157
  const runtime = isShell && parsed ? await ideRuntime(home) : null;
168
158
  const call = runtime && sdkCommand(parsed, runtime);
169
159
  if (call) {
@@ -174,7 +164,6 @@ async function beforeTool(event, state, home) {
174
164
  return output('PreToolUse', { permissionDecision: 'allow', updatedInput: prepareIdeShellInput(event.tool_name,
175
165
  event.tool_input, renderSdkCommand(call, runtime, state.sessionId), true) });
176
166
  }
177
- state.active = true;
178
167
  if (call.args[0] !== 'configure' && call.args[0] !== 'unlock') await bindIdeAccount(state, home);
179
168
  if (state.phase === 'setup' && !unlocked && !isManagementCall(call)) return denyBlocked(state, event, 'Entrorove:自举阶段只放行 Entrorove 管理调用及主机明确授权的任务间报告;业务与写操作需先建立原 flow。');
180
169
  if (call.args[0] === 'unlock') {
@@ -250,10 +239,11 @@ async function beforeTool(event, state, home) {
250
239
  return denyBlocked(state, event, `Entrorove:已阻止原生 ${event.tool_name ?? 'unknown'} 调用。当前阶段 ${state.phase}${state.taskNumber ? `,云端任务 ${state.taskNumber}` : ''}。写入、删除、移动及不确定的终端操作必须使用原任务的 guarded-write/guarded-delete/guarded-move 或已批准的执行计划。`);
251
240
  }
252
241
  export async function runIdeHook(event, { home } = {}) {
253
- return ideSession(event, async state => {
242
+ if (!await appliesIdeHook(event, home)) return {};
243
+ try { return await ideSession(event, async state => {
254
244
  const name = event.hook_event_name;
255
- const selected = name === 'UserPromptSubmit' && selectPrompt.test(userPrompt(event));
256
- const newSelection = selected && !state.active;
245
+ const selected = selectsIdeHook(event);
246
+ const newSelection = selected && !hasIdeHookSelection(state);
257
247
  if (name === 'UserPromptSubmit') {
258
248
  state.turnSequence = (Number.isSafeInteger(state.turnSequence) ? state.turnSequence : 0) + 1;
259
249
  delete state.stopNoticeTurn;
@@ -265,6 +255,7 @@ export async function runIdeHook(event, { home } = {}) {
265
255
  }
266
256
  if (selected) {
267
257
  state.active = true;
258
+ state.hookSelection = { source: 'user-prompt', sessionId: state.sessionId, repositoryRoot: state.repositoryRoot };
268
259
  if (['ended', 'user-exited'].includes(state.phase)) {
269
260
  if (state.phase === 'ended') { delete state.flowId; delete state.taskId; delete state.taskNumber; delete state.requestId; }
270
261
  state.phase = state.exitPhase ?? 'entry'; delete state.exitPhase;
@@ -327,7 +318,7 @@ export async function runIdeHook(event, { home } = {}) {
327
318
  }
328
319
  }
329
320
  return state.active && ['SessionStart','UserPromptSubmit','PostCompact','PreCompact'].includes(name) ? output(name, { additionalContext: `${ideContext(state)}${ideQueueContext(state)}` }) : {};
330
- }, home);
321
+ }, home); } catch (cause) { cause.entroroveHookSelected = true; throw cause; }
331
322
  }
332
323
  let incomingEvent;
333
324
  async function readHookEvent() {
@@ -349,11 +340,13 @@ async function main() {
349
340
  process.stdout.write(`${JSON.stringify(result)}\n`);
350
341
  }
351
342
  if (process.argv[1] && realpathSync(process.argv[1]) === fileURLToPath(import.meta.url)) main().catch(cause => {
352
- // A non-zero generic exit may fail open in the host. Always return a supported
353
- // explicit denial, without including arguments, prompts, credentials or paths.
343
+ // Deny only a positively selected session. Unidentified transport failures
344
+ // must not stop unrelated host work or expose input, credentials or paths.
354
345
  const reason = Object.hasOwn(inputErrors, cause?.code) ? inputErrors[cause.code]
355
346
  : `Entrorove 安全检查失败;本次工具调用已阻止。可复制解锁命令:${unlockCommand}`;
356
- const result = incomingEvent === 'Stop' ? { decision: 'block', reason } : incomingEvent === 'PreToolUse' ? deny(reason)
357
- : { continue: false, stopReason: reason, systemMessage: reason };
347
+ const result = !cause.entroroveHookSelected ? { systemMessage: Object.hasOwn(inputErrors, cause?.code)
348
+ ? inputErrors[cause.code] : 'Entrorove hook 无法确认选用会话;本次未接管,原状态已保留。' }
349
+ : incomingEvent === 'Stop' ? { decision: 'block', reason } : incomingEvent === 'PreToolUse' ? deny(reason)
350
+ : { continue: false, stopReason: reason, systemMessage: reason };
358
351
  process.stdout.write(`${JSON.stringify(result)}\n`);
359
352
  });
package/ide-install.mjs CHANGED
@@ -5,7 +5,7 @@ import { ideDirectory, ideDigest, readIdeJson, writeIdeJson, ideError } from './
5
5
  import { withStateLock } from './local-state-lock.mjs';
6
6
 
7
7
  const source = path.dirname(fileURLToPath(import.meta.url));
8
- const files = ['ide-hook.mjs', 'ide-session.mjs', 'ide-exit.mjs', 'break-glass.mjs', 'ide-results.mjs', 'ide-local-acceptance.mjs', 'ide-queue.mjs', 'ide-command.mjs', 'ide-code-mode.mjs', 'remote-transport.mjs', 'local-credential.mjs', 'local-state-lock.mjs', 'local-stage-boundary.mjs'];
8
+ const files = ['ide-hook.mjs', 'ide-session.mjs', 'ide-selection.mjs', 'ide-exit.mjs', 'break-glass.mjs', 'ide-results.mjs', 'ide-local-acceptance.mjs', 'ide-queue.mjs', 'ide-command.mjs', 'ide-code-mode.mjs', 'remote-transport.mjs', 'local-credential.mjs', 'local-state-lock.mjs', 'local-stage-boundary.mjs'];
9
9
  const quote = text => `'${text.replace(/'/gu, "'\\''")}'`;
10
10
 
11
11
  // One shared host-agnostic hook binary; every host is described by its own
@@ -96,7 +96,7 @@ async function directoryExists(directory) {
96
96
  }
97
97
  export async function activeIdeAdapters({ home, environment = process.env } = {}) {
98
98
  const directory = await ideDirectory(home).catch(cause => {
99
- if (cause.code === 'ENOENT') return null;
99
+ if (['ENOENT', 'ENTROROVE_CREDENTIAL_MISSING'].includes(cause.code)) return null;
100
100
  throw cause;
101
101
  });
102
102
  if (!directory) return [];
@@ -166,12 +166,12 @@ export async function installIdeHooks({ home, version, packageDirectory = source
166
166
  message: `已写入 Entrorove Hook 配置(${installed.map(item => item.host).join('、')});需由 IDE 信任并收到真实回调后才能确认生效。` };
167
167
  }
168
168
 
169
- export async function uninstallIdeHooks({ home }) {
170
- const directory = await ideDirectory(home).catch(error => { if (error.code === 'ENOENT') return null; throw error; });
169
+ export async function uninstallIdeHooks({ home, adapters = ideAdapters }) {
170
+ const directory = await ideDirectory(home).catch(error => { if (['ENOENT', 'ENTROROVE_CREDENTIAL_MISSING'].includes(error.code)) return null; throw error; });
171
171
  if (!directory) return { removed: [], preserved: [] };
172
172
  const removed = [], preserved = [], records = [];
173
173
  const hookPath = path.join(directory, 'ide-hook.mjs');
174
- for (const adapter of ideAdapters) {
174
+ for (const adapter of adapters) {
175
175
  const recordPath = path.join(directory, adapter.record), record = await readIdeJson(recordPath);
176
176
  if (record && (record.schemaVersion !== 'entrorove.ide-installation/1.0' || record.host !== adapter.host)) {
177
177
  throw ideError('ENTROROVE_IDE_STORE', `${adapter.host} installation record is invalid; it was preserved`);
@@ -206,6 +206,8 @@ export async function uninstallIdeHooks({ home }) {
206
206
  if (record) await fs.rm(recordPath);
207
207
  });
208
208
  }
209
+ // Disabling Codex must not remove the shared runtime used by another host.
210
+ if ((await fs.readdir(directory)).some(name => /^installation(?:-[a-z0-9-]+)?\.json$/u.test(name))) return { removed, preserved };
209
211
  const digests = new Map();
210
212
  for (const record of records) for (const [name, digest] of Object.entries(record.files ?? {})) {
211
213
  if (!digests.has(name)) digests.set(name, new Set());
@@ -0,0 +1,31 @@
1
+ import { promises as fs } from 'node:fs';
2
+ import path from 'node:path';
3
+ import { ideDirectory, ideDigest, readIdeJson } from './ide-session.mjs';
4
+ import { explicitIdeExit } from './ide-exit.mjs';
5
+
6
+ const selection = /^(?:(?:请)?(?:使用|启用)\s+\$?entrorove|\$entrorove)(?:\s|[,。,::!!]|$)/iu;
7
+ export function ideUserPrompt(event) {
8
+ let prompt = typeof event.prompt === 'string' ? event.prompt.trim() : '';
9
+ for (const wrapper of ['**', '__', '`']) {
10
+ if (prompt.startsWith(wrapper) && prompt.endsWith(wrapper)) prompt = prompt.slice(wrapper.length, -wrapper.length).trim();
11
+ }
12
+ return prompt;
13
+ }
14
+ export const selectsIdeHook = event => event.hook_event_name === 'UserPromptSubmit' && selection.test(ideUserPrompt(event));
15
+ export const hasIdeHookSelection = state => state?.active === true && state.hookSelection?.source === 'user-prompt'
16
+ && state.hookSelection.sessionId === state.sessionId && state.hookSelection.repositoryRoot === state.repositoryRoot;
17
+
18
+ // Global callbacks must be inert until this exact session and repository opt in.
19
+ // In particular, discovery, an old active bit, and a broken store cannot select it.
20
+ export async function appliesIdeHook(event, home) {
21
+ if (typeof event?.session_id !== 'string' || !/^[A-Za-z0-9_-]{1,200}$/u.test(event.session_id)
22
+ || typeof event.cwd !== 'string' || !path.isAbsolute(event.cwd)) return false;
23
+ if (selectsIdeHook(event)) return true;
24
+ try {
25
+ const directory = await ideDirectory(home);
26
+ const state = await readIdeJson(path.join(directory, `session-${ideDigest(event.session_id)}.json`));
27
+ const exiting = event.hook_event_name === 'UserPromptSubmit' && explicitIdeExit(ideUserPrompt(event));
28
+ return state?.sessionId === event.session_id && (hasIdeHookSelection(state) || exiting)
29
+ && await fs.realpath(event.cwd) === state.repositoryRoot;
30
+ } catch { return false; }
31
+ }
package/ide-session.mjs CHANGED
@@ -152,8 +152,11 @@ async function declaredHost(environment) {
152
152
  return null;
153
153
  }
154
154
  export async function assertIdeExecution(args, input, { home = os.homedir(), environment = process.env } = {}) {
155
+ approvedResultContext = undefined;
156
+ if (environment.ENTROROVE_HOOK_MODE === undefined) return { mode: 'explicit-sdk', hookEnforced: false };
157
+ if (environment.ENTROROVE_HOOK_MODE !== 'required') throw ideError('ENTROROVE_IDE_MODE', 'Unknown Entrorove hook execution mode');
155
158
  const { host, sessionId } = ideGuardBinding(environment);
156
- if (!host || host === 'Entrorove CLI') return;
159
+ if (!host || host === 'Entrorove CLI') throw ideError('ENTROROVE_IDE_GUARD_UNSUPPORTED', 'Hook execution requires its actual IDE host and session');
157
160
  const lifecycle = args[0] === 'local' ? args[1] : args[0] === 'invoke' ? input?.operation : null;
158
161
  if (['flow-cancel', 'flow-abandon'].includes(lifecycle) && await readBreakGlass(home, sessionId)) {
159
162
  const flowId = args[0] === 'invoke' ? input?.input?.flowId : input?.flowId;
@@ -177,13 +180,15 @@ export async function assertIdeExecution(args, input, { home = os.homedir(), env
177
180
  const directory = await ideDirectory(home), state = await readIdeJson(path.join(directory, `session-${ideDigest(sessionId)}.json`));
178
181
  const expected = ideDigest({ args, input });
179
182
  const approved = [state?.pendingCall, ...Object.values(state?.readCalls ?? {})].find(call => call?.inputDigest === expected);
180
- if (!state?.active || !approved || state.sessionId !== sessionId
183
+ if (!state?.active || state.hookSelection?.source !== 'user-prompt' || state.hookSelection.sessionId !== sessionId
184
+ || state.hookSelection.repositoryRoot !== state.repositoryRoot || !approved || state.sessionId !== sessionId
181
185
  || Date.now() - Date.parse(state.observedAt) > 120_000) {
182
186
  throw ideError('ENTROROVE_IDE_GUARD_REQUIRED', `${host} tool guard has not approved this exact call. Run install, accept the host hook, and resume the original task.`);
183
187
  }
184
188
  await bindIdeAccount(state, home);
185
189
  approvedResultContext = { home, sessionId: state.sessionId, repositoryRoot: state.repositoryRoot,
186
190
  accountDigest: state.accountDigest, toolUseId: approved.toolUseId, inputDigest: expected };
191
+ return { mode: 'hook-enforced', hookEnforced: true };
187
192
  }
188
193
 
189
194
  export async function settleBreakGlassSession(args, input, result, { home = os.homedir(), environment = process.env } = {}) {
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.60",
4
+ "version": "0.1.61",
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.60",
3
+ "version": "0.1.61",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "entrorove": "cli.mjs"
@@ -222,6 +222,7 @@
222
222
  "ide-code-mode.mjs",
223
223
  "ide-command.mjs",
224
224
  "ide-hook.mjs",
225
+ "ide-selection.mjs",
225
226
  "ide-install.mjs",
226
227
  "install-cleanup.mjs",
227
228
  "ide-session.mjs",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "schemaVersion": "entrorove.release-state/1.0",
3
- "version": "0.1.60",
4
- "digest": "e17ae66b1a55cf8a12f750bc8211a083ecb15c6625687e7d17b7ea08a4b302a1",
3
+ "version": "0.1.61",
4
+ "digest": "8211a23e233808dfc8f43dd0b28af5eae9b8bea5127b9550da8e8c50d9447052",
5
5
  "files": {
6
6
  "connected-cli.mjs": "79c3359303957a9ebb8987e3dfb181f189d380fa02ef4462683b786aedfc7802",
7
7
  "local-approved-process.mjs": "442856df788e63f38757439ba0657d1c689aaf797631a955fae3dd639e844e6d",
@@ -22,10 +22,10 @@
22
22
  "mutation-client.mjs": "6455c35b483d6d22e42e7be708630db07d9fd1d3cdf639294b126b69db2eae2f",
23
23
  "cloud-mutation-authority.mjs": "c7226e882a4f3be4d2f55728f515d5a815c0aa1f21aa5e4689314e6e6a8c66a2",
24
24
  "ide-code-mode.mjs": "f8ba4de33632ab4c6df52dab52f392414b4632c7e94b218b8065677970c075c7",
25
- "ide-command.mjs": "4f61da5a39f88b8ba657d6b1beeb76f765c7f37f3d3e7f52dd9d2eb398499ec3",
26
- "ide-hook.mjs": "037c245cd2b261acbaed126d5441a2b64a91bc9733a150ebd1ecfdf531d8c49f",
27
- "ide-install.mjs": "7effa871a44e1215e26b913edc3de0c5b36deaff1a57c0f20d33212160a63f22",
28
- "ide-session.mjs": "eb8021bc6e38e5c9c35d1dc4cc0015178960cb4eeb39da5a4de47762bf8d7c95",
25
+ "ide-command.mjs": "dbdb4cbf8e870c93a746ca176cbbdeffc0458de1293583d3eb9af4c256541370",
26
+ "ide-hook.mjs": "c44d37997fae55bd113ff9044947c54152aaccdf081d0f39d437179d33e9a4e2",
27
+ "ide-install.mjs": "1c80e191ddc2ac8789f0c71edc7bc8c3c8cea6bfab537cdf6baffa4a6ce3419c",
28
+ "ide-session.mjs": "5277a6ea3445bfa9b2e28847372d963da77787d472d0414c0e253d4fe29f74ec",
29
29
  "break-glass.mjs": "6ec9f97c5d68bb33121b2374c06b9d4c8eb3419b52d00849fec7124bc63e4582",
30
30
  "ide-results.mjs": "baa0618845ced901334644479bf4e0d298bad3f955bc18846752de6c283a5573",
31
31
  "ide-local-acceptance.mjs": "238eec6d7b6093fb8ad022da0ed3383a48ba2ffcf693e3ce8a241cf9624088d8",
@@ -50,7 +50,7 @@
50
50
  "runtime/validator-runtime-v7.0.44.d.mts": "7797edbfe2f7de24da370204dce4dfd22822a1bd80df93514fff900b98afe36f",
51
51
  "snapshot-retention.mjs": "ca46aeb36b1a5a24fe7e967031a71b9a3d33f40cb715d344402cbe0dc9c425cb",
52
52
  "snapshot-cleanup-journal.mjs": "76a9684cc95b4eb076e1298d78a9dff37948b00d9607b8f546c599072d3ed331",
53
- "IDE-EXECUTION.md": "12bff5711e9100ffcab03409bdf2b587e4a2d3f464500dbaa48ff59786d9cf42",
53
+ "IDE-EXECUTION.md": "203aa4ae47cc45915e1a89bd7fbb56bf57aebccef444599b3a2d1d2816f01907",
54
54
  "REUSE-DISCIPLINE.md": "5b214cf313e7f715f52d54e28093a2d038f0f196ca64b15d1c49587b9d8eecf8",
55
55
  "reuse-discipline-release.mjs": "7f55ae10ec3396ed3bc5430a0c64f94c33c2dfbc57b32084caab92fd5fd1586f",
56
56
  "local-final-gate16.mjs": "083aec470670e6cdb7ad2ab27b7a51b53a9ec3a8b4121cb9cbfa17d9940892c2",
@@ -94,18 +94,19 @@
94
94
  "local-autocoord17-waits.mjs": "2b0a713987822548a94e1ed3a9a891532db45383797dfc5d5bc8d1e35593cf25",
95
95
  "local-validator-isolation17.mjs": "48e94604e28b8fddd567dfb35ff60b73fd094a5803bd49f076388b7f310f92aa",
96
96
  "local-calctool-composition17.mjs": "ac89fcb41ab0d0d9b47c1b5696945281b0894eb064820775e36a011c76314e6a",
97
- "cli.mjs": "d0f29034c812f6d2ebfecdfcddacce70334ab379e3d25dc673a72ece817a977c",
97
+ "cli.mjs": "b28bfda3d9ec610c7865725eff4e5d551bcbdfc8847f6fd43eca845ce3881ea8",
98
+ "ide-selection.mjs": "e11e2b363c06444f1dcb4cdab1eaa0eb748a379b84354e0e71fd9c2dc79a4815",
98
99
  "ide-exit.mjs": "c890c7b34fa6f12e0ccff7a7b85738d515fb13ad93f671a8547ae6570b66488d",
99
100
  "install-cleanup.mjs": "8a8e1a7dfe37427f86cd4d6e317890d0829894a9949d4216674a716b36edbc54",
100
101
  "review-client.mjs": "3491eef68ed533174b31db3c53febb6519b8f088f71a9964644714a9a4c03c77",
101
102
  "version.mjs": "1f392147d72c40af4b03b307ea607075fb815af5174fa07e254b95fe12dbf3e7",
102
- "SKILL.md": "f6e8b3f64d102063b8e6e263b3422563ed3dd4781c7dd5c46a1c6bccef02ffad",
103
+ "SKILL.md": "f04566431da716ff61812118004ca69f2a8f9f24fe4e032f2a1fff5073b8071b",
103
104
  "CAPABILITY-MAP.md": "e420bbc202c27d983635ae33b823b39dd445cf5cdf40ffe88e210a755cb7424d",
104
105
  "AGREEMENT.md": "f93a68e92c457200dd024610e65f74e478b8f60860f951da818b70561f1b9bf8",
105
106
  "DELIVERY-BOUNDARIES.md": "9b9db50ed1bcce9e77ff6527b6bb7eaf12f3b913162ea9cb7fe90db06b393a4f",
106
- "package.json": "5da583a9c348c5acedbd8a97c9ed0b3ba3844bb8b6445c7b3d78690981f612ba",
107
+ "package.json": "ce0f78910ec9aeb36a7d61a30a68c6b117c9b29ca34cc8446a46604b251c131e",
107
108
  "manifest.json": "ab06e307795bc9da5add926752b6c4dbdaf18625090261a035aa356a679c504f",
108
- "release.mjs": "b404798d90a4447740857a58a7656ecd80b51c36971561d822961913793e101b",
109
+ "release.mjs": "90e4c5beb05e263c2cacf09c476480ac3599a965d62e0adda68226a8baaa968c",
109
110
  "local-cli.mjs": "41e2a737c8587cb5ce469a7661f6d14b5ea4cd073f8286822fc4dc96b67f56a1",
110
111
  "local-state-lock.mjs": "1dbf64264642a0851faf043e07ee9f66d2ad0508e9f799b0626246e42fae893e",
111
112
  "local-execution.mjs": "185f34e917440a360b3c4bafd3af245243807d964ae3820b3cffaaa2e6348cc6",
package/release.mjs CHANGED
@@ -68,7 +68,7 @@ const sourceFiles = [
68
68
  'local-merge-transaction.mjs',
69
69
  ...swarmSourceFiles,
70
70
  ...coordination17SourceFiles,
71
- 'cli.mjs', 'ide-exit.mjs', 'install-cleanup.mjs', 'review-client.mjs', 'version.mjs', 'SKILL.md', 'CAPABILITY-MAP.md', 'AGREEMENT.md', 'DELIVERY-BOUNDARIES.md',
71
+ 'cli.mjs', 'ide-selection.mjs', 'ide-exit.mjs', 'install-cleanup.mjs', 'review-client.mjs', 'version.mjs', 'SKILL.md', 'CAPABILITY-MAP.md', 'AGREEMENT.md', 'DELIVERY-BOUNDARIES.md',
72
72
  'package.json', 'manifest.json', 'release.mjs',
73
73
  'local-cli.mjs', 'local-state-lock.mjs', 'local-execution.mjs', 'local-integrity.mjs', 'local-evidence-verification.mjs', 'local-formula.mjs', 'local-ledger.mjs',
74
74
  'local-error.mjs',
package/releases.json CHANGED
@@ -1,4 +1,10 @@
1
1
  [
2
+ {
3
+ "version": "0.1.61",
4
+ "date": "2026-10-18",
5
+ "zh": "钩子改为可选:默认安装关闭钩子,IDE 可直接调用 SDK;只有明确选用技能的原会话与仓库启用拦截,普通查询、旧激活状态及无法归属的事件不再接管其他任务。保留账号、写入范围、签名和真实本地验收校验。",
6
+ "en": "Make hooks optional: default installs disable hooks and IDEs can call the SDK directly. Interception requires explicit selection of the original session and repository; discovery, legacy activation and unidentified events cannot govern unrelated work. Preserve account, mutation scope, signatures and actual local acceptance checks."
7
+ },
2
8
  {
3
9
  "version": "0.1.60",
4
10
  "date": "2026-10-17",