@entrorove/skill 0.1.38 → 0.1.39
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/CAPABILITY-MAP.md +1 -1
- package/CHANGELOG.md +5 -0
- package/IDE-EXECUTION.md +4 -0
- package/SKILL.md +3 -1
- package/cli.mjs +5 -5
- package/ide-command.mjs +1 -1
- package/ide-hook.mjs +19 -3
- package/ide-install.mjs +3 -2
- package/ide-queue.mjs +108 -0
- package/manifest.json +1 -1
- package/package.json +2 -1
- package/release-state.json +12 -11
- package/releases.json +6 -0
- package/reuse-discipline-release.mjs +1 -1
package/CAPABILITY-MAP.md
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
| 并行路径等价 | `local run-approved-plan`、Validator 7.0.44 | 固定基线来自本项目既有路径的实际输出;逐字段比对,缺证据不得 pass,差异列出字段。不是其它项目的历史输出 |
|
|
10
10
|
| 结束清理与回传 | 原请求 `continue`、`workflow-verdict`、反馈确认 | 仅验证完成且回传已存后清理同流程冗余快照,保留原始及最新恢复点;公开技术评分按真实已确认回执聚合 |
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
任务优先级队列已接入 IDE 私有状态:新用户原文先进入 `queue status`,再按已有四级优先级通过 `queue route` 排队或在安全检查点插入;插入任务完成并取得真实回执后恢复原 flow、taskId、taskNumber 与 requestId。队列不承诺主机断电或 IDE 进程退出时的无人值守执行。源代码执行仍需原合同配置的可信运行器,范围签名器本身不授予执行权限。
|
|
13
13
|
|
|
14
14
|
## 2026-10-13 接入:协作观测、原生规划与任务通知
|
|
15
15
|
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.39 — 2026-10-14
|
|
4
|
+
|
|
5
|
+
- 增加受控需求队列,支持按优先级排队或在安全检查点插入;插入任务完成并回传真实确认后自动恢复原任务与原编号。
|
|
6
|
+
- Added a controlled requirement queue with priority ordering and checkpointed interruption; after verified acknowledged completion, the original task and identifiers resume automatically.
|
|
7
|
+
|
|
3
8
|
## 0.1.38 — 2026-10-14
|
|
4
9
|
|
|
5
10
|
- 新增需宿主信任的 Codex 工具守卫,保留原任务和请求编号;接入确定性复用纪律、用途审查、变更分类、逐字段等价与全仓差集验收;仅验证完成并回传后清理冗余快照,按真实回执展示技术评分;更新日志每页 5 条。
|
package/IDE-EXECUTION.md
CHANGED
|
@@ -22,6 +22,10 @@ When a bounded watch returns `nextAction`, continue it with the returned `afterI
|
|
|
22
22
|
|
|
23
23
|
Set `ENTROROVE_IDE` to the actual host name (for example `Codex`, `Cursor` or `Claude Code`) when host detection is unavailable. It carries only the IDE label; credentials remain in the restricted store. Use the latest official package with `--prefer-online` for new invocations, and reread the managed `SKILL.md` after installation reports an update. Recovering an uncertain request remains a read-only query of its original ID.
|
|
24
24
|
|
|
25
|
+
### Interrupting and resuming requirements
|
|
26
|
+
|
|
27
|
+
User text received during an unfinished governed task is persisted as an exact, private queue entry. `queue status` reads it; `queue route` assigns the existing `background`, `normal`, `high` or `urgent` priority. `interrupt` only takes effect at a controlled-call checkpoint and keeps the parent flow, task ID, task number, request ID, repository and account binding. Once the inserted requirement has a verified completion and stored feedback acknowledgement, the parent is restored and its returned `nextStep` is the only continuation. The guard blocks Stop while an entry is unclassified, a call is unresolved or a runnable parent/child remains. The queue does not promise execution when the host is powered off or the IDE process is unavailable.
|
|
28
|
+
|
|
25
29
|
Send JSON on standard input to `npx @entrorove/skill invoke`. Before every execution call, discover `operation: "capabilities"` with `{}` and validate input against the returned operation schema. The CLI performs remote discovery automatically; local adapters discover the installed contract before execution. Discovery failure stops execution. Adapters that expose the four-step task route can call `task-describe`, `message-route`, `message-accept` and `task-resume` in order; `start` remains the one-call workflow equivalent. Then start with `operation: "start"` and an `input.task` containing the goal, work type, risk, difficulty and observed scope facts. Work types are `implementation`, `documentation`, `calculation`, `merge`, `review`, `decision` and `diagnosis`. Assess facts from the real task and repository; do not invent certainty. entrorove chooses the workflow. A small task may return `decision: "direct"`, which skips extra planning round trips. It does not authorize native writes outside the controlled task; keep its executable acceptance assertion and the original mutation authority. The public envelope is `entrorove.skill.request/2.0`; `requestId` is the SHA-256 digest of `operation + canonical(input)`. Version 1.0 remains accepted for older callers.
|
|
26
30
|
|
|
27
31
|
The server's ordinary `start` operation creates a durable skill flow and counts a skill invocation. The IDE CLI routes new starts through the account's preference and explicit task choice described above. A real sandbox is created only by `/api/cloud-task`, which returns both `taskId` and `taskNumber` and counts sandbox usage separately. `local` changes only this task's route. Disabled entitlement or missing model prerequisites stop cloud dispatch; a `needs-input`, `blocked` or `failed` result must never be described as sandbox entry. Do not send a second ordinary `start` after cloud acceptance; continue the original bound flow returned by its watch/status.
|
package/SKILL.md
CHANGED
|
@@ -5,7 +5,7 @@ description: Execute an engineering task through Entrorove, preserving its origi
|
|
|
5
5
|
|
|
6
6
|
# entrorove · 熵巡
|
|
7
7
|
|
|
8
|
-
Version: 0.1.
|
|
8
|
+
Version: 0.1.39.
|
|
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
|
|
|
@@ -22,6 +22,8 @@ Entrorove applies to the whole task once selected. Reading this document or call
|
|
|
22
22
|
|
|
23
23
|
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.
|
|
24
24
|
|
|
25
|
+
When a new user requirement arrives while the original task is unfinished, store its exact text in the private IDE queue. Use `queue` with `{"action":"status"}` to inspect it, then `{"action":"route","messageId":"...","mode":"queued"|"interrupt","priority":"background"|"normal"|"high"|"urgent"}`. An urgent interrupt is admitted only at a completed controlled-call checkpoint; it saves the original flow, task UUID, homepage number and request ID. After the inserted task reaches a verified end acknowledgement, the queue restores that exact parent context and its next operation. Unclassified text, unresolved calls and unknown outcomes block ending the turn. Queue state is bounded, account/session/repository-bound and survives compaction and process restart; it cannot wake a powered-off host.
|
|
26
|
+
|
|
25
27
|
## Operations and visible progress
|
|
26
28
|
|
|
27
29
|
- Invoke service operations with JSON stdin to `npx @entrorove/skill invoke` or a host-approved rewritten equivalent.
|
package/cli.mjs
CHANGED
|
@@ -442,6 +442,8 @@ async function main() {
|
|
|
442
442
|
await install();
|
|
443
443
|
const accepted = await writeAgreementAcceptance(await stdinJson(), os.homedir());
|
|
444
444
|
process.stdout.write(JSON.stringify({ accepted, version: agreementVersion }) + '\n');
|
|
445
|
+
} else if (command === 'queue' && args.length === 0) {
|
|
446
|
+
process.stdout.write(JSON.stringify(await (await import('./ide-queue.mjs')).runIdeQueue(await stdinJson())) + '\n');
|
|
445
447
|
} else if (command === 'invoke' && args.length === 0) {
|
|
446
448
|
await invoke(await stdinJson());
|
|
447
449
|
} else if (command === 'native' && args.length === 2) {
|
|
@@ -487,14 +489,12 @@ async function main() {
|
|
|
487
489
|
} else if (command === 'local') {
|
|
488
490
|
await runLocal(args);
|
|
489
491
|
} else {
|
|
490
|
-
throw new Error('Usage: entrorove install | configure | agreement | invoke | 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 | review | local (JSON on stdin)');
|
|
492
|
+
throw new Error('Usage: entrorove 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 | review | local (JSON on stdin)');
|
|
491
493
|
}
|
|
492
494
|
}
|
|
493
|
-
if (process.argv[1] && realpathSync(process.argv[1]) === realpathSync(fileURLToPath(import.meta.url))) {
|
|
494
|
-
const { captureIdeResults } = await import('./ide-results.mjs');
|
|
495
|
+
if (process.argv[1] && realpathSync(process.argv[1]) === realpathSync(fileURLToPath(import.meta.url))) { const { captureIdeResults } = await import('./ide-results.mjs');
|
|
495
496
|
const finish = captureIdeResults();
|
|
496
|
-
main().catch((error) => {
|
|
497
|
-
process.stderr.write(`${error instanceof SyntaxError ? 'Invalid JSON data' : error.message}\n`);
|
|
497
|
+
main().catch((error) => { process.stderr.write(`${error instanceof SyntaxError ? 'Invalid JSON data' : error.message}\n`);
|
|
498
498
|
process.exitCode = 1;
|
|
499
499
|
}).finally(finish).catch(() => { process.stderr.write('Entrorove IDE completion could not be saved; recover the original request.\n'); process.exitCode = 1; });
|
|
500
500
|
}
|
package/ide-command.mjs
CHANGED
|
@@ -38,7 +38,7 @@ export function sdkCommand(parsed, runtime) {
|
|
|
38
38
|
if (!['@entrorove/skill', '@entrorove/skill@latest', `@entrorove/skill@${runtime.version}`].includes(words.shift())) return null;
|
|
39
39
|
args = words;
|
|
40
40
|
} else return null;
|
|
41
|
-
const simple = new Set(['invoke', 'cloud-task', 'cloud-status', 'cloud-watch', 'agreement', 'configure']);
|
|
41
|
+
const simple = new Set(['invoke', 'cloud-task', 'cloud-status', 'cloud-watch', 'agreement', 'configure', 'queue']);
|
|
42
42
|
if (simple.has(args[0]) && args.length === 1 && parsed.input) return { args, input: parsed.input };
|
|
43
43
|
if (['status', 'feedback'].includes(args[0]) && /^[a-f0-9]{64}$/u.test(args[1] ?? '') && [2,3].includes(args.length)) return { args };
|
|
44
44
|
if (['flows', 'install'].includes(args[0]) && args.length === 1 && !parsed.input) return { args };
|
package/ide-hook.mjs
CHANGED
|
@@ -7,6 +7,8 @@ import { inspectIdeCodeMode } from './ide-code-mode.mjs';
|
|
|
7
7
|
import { pathToFileURL } from 'node:url';
|
|
8
8
|
import path from 'node:path';
|
|
9
9
|
import { parseIdeResults, consumeIdeResults } from './ide-results.mjs';
|
|
10
|
+
import { registerIdeRequirement, ideQueueContext, unclassifiedIdeRequirements,
|
|
11
|
+
advanceIdeQueue, checkQueuedStart, unfinishedIdeQueue } from './ide-queue.mjs';
|
|
10
12
|
|
|
11
13
|
const output = (event, extra = {}) => ({ hookSpecificOutput: { hookEventName: event, ...extra } });
|
|
12
14
|
const deny = reason => output('PreToolUse', { permissionDecision: 'deny', permissionDecisionReason: reason });
|
|
@@ -71,7 +73,9 @@ async function beforeTool(event, state, home) {
|
|
|
71
73
|
const newStart = call.args[0] === 'cloud-task' || call.args[0] === 'invoke' && ['start','cloud-task'].includes(call.input?.operation);
|
|
72
74
|
if (newStart && (state.flowId || state.taskId) && state.phase !== 'ended') return deny('Entrorove:必须继续原任务,禁止用第二次 start 或 cloud-task 绕过原门禁。');
|
|
73
75
|
const discovery = ['invoke','local'].includes(call.args[0]) && ['capabilities','help'].includes(call.input?.operation ?? call.args[1]);
|
|
74
|
-
const observer = discovery || ['status','feedback','cloud-status','configure','agreement','install'].includes(call.args[0]);
|
|
76
|
+
const observer = discovery || ['status','feedback','cloud-status','configure','agreement','install','queue'].includes(call.args[0]);
|
|
77
|
+
if (unclassifiedIdeRequirements(state) && !observer) return deny('Entrorove:新需求已保存,先用 queue status 读取并按轻重缓急 route 分类;禁止丢弃新需求或覆盖原任务。');
|
|
78
|
+
if (newStart && !checkQueuedStart(state,call)) return deny('Entrorove:新任务目标必须与实际进入队列的用户原文一致。');
|
|
75
79
|
if (call.args[0] === 'local' && !discovery && !state.flowId) return deny('Entrorove:本地执行前必须先受理原任务并绑定账号、flowId 与仓库;不能直接使用其它流程的通行证。');
|
|
76
80
|
if (state.pendingCall && !observer) return deny('Entrorove:原调用尚未收到结束回执;先只读恢复其状态,禁止并发重发。');
|
|
77
81
|
const input = call.args[0] === 'invoke' ? call.input?.input : call.input;
|
|
@@ -79,6 +83,7 @@ async function beforeTool(event, state, home) {
|
|
|
79
83
|
if (call.args[0] === 'scope-connect' && call.args[1] !== state.repositoryRoot
|
|
80
84
|
|| call.args[0] === 'local' && (input?.repositoryRoot && input.repositoryRoot !== state.repositoryRoot || call.args[2] && call.args[2] !== state.repositoryRoot)) return deny('Entrorove:调用不能替换本任务的实际仓库。');
|
|
81
85
|
let requestId;
|
|
86
|
+
if (newStart) state.taskGoal = call.args[0] === 'cloud-task' ? call.input?.goal : call.input?.input?.task?.goal;
|
|
82
87
|
if (call.args[0] === 'invoke' && !call.input.operation.startsWith('cloud-')) {
|
|
83
88
|
const { buildInvocation } = await import(pathToFileURL(path.join(path.dirname(runtime.cli), 'remote-client.mjs')).href);
|
|
84
89
|
requestId = buildInvocation(call.input.operation, call.input.input).requestId;
|
|
@@ -113,6 +118,7 @@ export async function runIdeHook(event, { home } = {}) {
|
|
|
113
118
|
state.active = true;
|
|
114
119
|
if (state.phase === 'ended') { delete state.flowId; delete state.taskId; delete state.taskNumber; delete state.requestId; state.phase = 'entry'; }
|
|
115
120
|
}
|
|
121
|
+
if (name === 'UserPromptSubmit') registerIdeRequirement(state,event);
|
|
116
122
|
if (name === 'PreToolUse') return beforeTool(event, state, home);
|
|
117
123
|
const pendingRead = state.readCalls?.[event.tool_use_id];
|
|
118
124
|
if (name === 'PostToolUse' && state.active && (state.pendingCall?.toolUseId === event.tool_use_id || pendingRead)) {
|
|
@@ -120,6 +126,10 @@ export async function runIdeHook(event, { home } = {}) {
|
|
|
120
126
|
const recorded = await consumeIdeResults(event, state, pending, home);
|
|
121
127
|
const visible = parseIdeResults(event.tool_response);
|
|
122
128
|
const values = [...recorded, ...visible];
|
|
129
|
+
if (pending.command==='queue') {
|
|
130
|
+
delete state.readCalls[event.tool_use_id];
|
|
131
|
+
return output(name,{additionalContext:`${ideContext(state)}${ideQueueContext(state)}`});
|
|
132
|
+
}
|
|
123
133
|
if (['install','configure','agreement'].includes(pending.command)) {
|
|
124
134
|
delete state.readCalls[event.tool_use_id];
|
|
125
135
|
const configured = values.some(value => pending.command === 'install' ? /^\d+\.\d+\.\d+$/u.test(value.version ?? '') && value.ide?.configured === true
|
|
@@ -143,10 +153,16 @@ export async function runIdeHook(event, { home } = {}) {
|
|
|
143
153
|
const messages = collectResult(state, bound, pending.command === 'local');
|
|
144
154
|
if (pendingRead) delete state.readCalls[event.tool_use_id];
|
|
145
155
|
if (!pendingRead || recoveredOriginal) delete state.pendingCall;
|
|
156
|
+
const resumed = await advanceIdeQueue(state,home);
|
|
157
|
+
if (resumed) messages.push(resumed);
|
|
146
158
|
return { ...output(name, { additionalContext: ideContext(state) }), ...(messages.length ? { systemMessage: messages.join('\n') } : {}) };
|
|
147
159
|
}
|
|
148
|
-
if (name
|
|
149
|
-
|
|
160
|
+
if (name==='Stop' && state.active) {
|
|
161
|
+
const resumed = await advanceIdeQueue(state,home);
|
|
162
|
+
if (resumed || unclassifiedIdeRequirements(state) || unfinishedIdeQueue(state) && !['blocked','needs-input'].includes(state.phase)
|
|
163
|
+
|| !['ended','blocked','needs-input','setup'].includes(state.phase)) return {decision:'block',reason:`${ideContext(state)}${ideQueueContext(state)}${resumed}原任务或队列尚未取得结束回执;继续可运行工作,真实阻断必须明确报告。`};
|
|
164
|
+
}
|
|
165
|
+
return state.active && ['SessionStart','UserPromptSubmit','PostCompact','PreCompact'].includes(name) ? output(name, { additionalContext: `${ideContext(state)}${ideQueueContext(state)}` }) : {};
|
|
150
166
|
}, home);
|
|
151
167
|
}
|
|
152
168
|
let incomingEvent;
|
package/ide-install.mjs
CHANGED
|
@@ -5,13 +5,14 @@ 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-results.mjs', 'ide-command.mjs', 'ide-code-mode.mjs', 'remote-transport.mjs', 'local-credential.mjs', 'local-state-lock.mjs'];
|
|
8
|
+
const files = ['ide-hook.mjs', 'ide-session.mjs', 'ide-results.mjs', 'ide-queue.mjs', 'ide-command.mjs', 'ide-code-mode.mjs', 'remote-transport.mjs', 'local-credential.mjs', 'local-state-lock.mjs'];
|
|
9
9
|
const events = ['SessionStart', 'UserPromptSubmit', 'PreToolUse', 'PostToolUse', 'PostCompact', 'Stop'];
|
|
10
10
|
const quote = text => `'${text.replace(/'/gu, "'\\''")}'`;
|
|
11
11
|
function definition(node, file) {
|
|
12
12
|
const command = `${quote(node)} ${quote(file)}`;
|
|
13
13
|
return { hooks: Object.fromEntries(events.map(event => [event, [{ hooks: [{ type: 'command', command,
|
|
14
|
-
timeout: 10, statusMessage: 'Entrorove · 检查任务',
|
|
14
|
+
timeout: 10, statusMessage: 'Entrorove · 检查任务',
|
|
15
|
+
...(['PostCompact','Stop'].includes(event) ? {} : {additionalContextLimit:1500}) }] }]])) };
|
|
15
16
|
}
|
|
16
17
|
async function prepareConfigDirectory(home) {
|
|
17
18
|
const directory = path.join(home, '.codex');
|
package/ide-queue.mjs
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { pathToFileURL } from 'node:url';
|
|
3
|
+
import { ideDigest, ideError, ideRuntime, ideSession } from './ide-session.mjs';
|
|
4
|
+
|
|
5
|
+
const fields = ['flowId','taskId','taskNumber','requestId','remoteRequestId','phase','nextStep','feedbackAcknowledged','taskGoal'];
|
|
6
|
+
const control = /^(?:继续(?:执行)?[.!。!]?|已信任|已验证|验证了|确认|知道了|进度|啥进度了[??]?|状态|continue[.!]?|status[?]?|confirmed|done)$/iu;
|
|
7
|
+
const fail = message => { throw ideError('ENTROROVE_IDE_QUEUE', message); };
|
|
8
|
+
const context = state => Object.fromEntries(fields.filter(key => Object.hasOwn(state,key)).map(key => [key,structuredClone(state[key])]));
|
|
9
|
+
function restore(state, saved) {
|
|
10
|
+
for (const key of fields) delete state[key];
|
|
11
|
+
Object.assign(state,structuredClone(saved));
|
|
12
|
+
}
|
|
13
|
+
function queue(state) {
|
|
14
|
+
state.queue ??= { schemaVersion:'entrorove.ide-queue/1.0', nextSequence:1, current:null, parents:[], entries:[], completed:[] };
|
|
15
|
+
const value = state.queue;
|
|
16
|
+
if (value.schemaVersion !== 'entrorove.ide-queue/1.0' || !Number.isSafeInteger(value.nextSequence)
|
|
17
|
+
|| !Array.isArray(value.parents) || !Array.isArray(value.entries) || !Array.isArray(value.completed)) fail('Original task queue is invalid; it was preserved');
|
|
18
|
+
return value;
|
|
19
|
+
}
|
|
20
|
+
async function priorities(home) {
|
|
21
|
+
const runtime = await ideRuntime(home);
|
|
22
|
+
return (await import(pathToFileURL(path.join(path.dirname(runtime.cli),'local-swarm-state.mjs')).href)).SWARM_PRIORITIES;
|
|
23
|
+
}
|
|
24
|
+
function ensureCurrent(state) {
|
|
25
|
+
const value = queue(state);
|
|
26
|
+
if (!value.current && (state.flowId || state.taskId)) value.current = { messageId:`original-${ideDigest({flowId:state.flowId,taskId:state.taskId})}`,
|
|
27
|
+
goal:state.taskGoal ?? null, priority:'normal', sequence:0, original:true };
|
|
28
|
+
return value;
|
|
29
|
+
}
|
|
30
|
+
export function registerIdeRequirement(state,event) {
|
|
31
|
+
if (!state.active || !(state.flowId || state.taskId || state.queue?.current)) return;
|
|
32
|
+
const text = (event.prompt ?? '').trim();
|
|
33
|
+
if (!text || control.test(text) || /^<send_user_message_question_reply>/u.test(text)) return;
|
|
34
|
+
const value = ensureCurrent(state);
|
|
35
|
+
const sourceId = event.prompt_id ?? event.user_message_id;
|
|
36
|
+
const messageId = `requirement-${ideDigest({sessionId:state.sessionId,sourceId:sourceId ?? value.nextSequence,text})}`;
|
|
37
|
+
if (value.entries.some(item=>item.messageId===messageId) || value.completed.some(item=>item.messageId===messageId)) return;
|
|
38
|
+
if (text.length>100_000 || value.entries.length>=64) fail('Task queue capacity reached; no new requirement was discarded. Resolve the original queue before admission.');
|
|
39
|
+
value.entries.push({messageId,goal:text,goalDigest:ideDigest(text),sequence:value.nextSequence++,status:'unclassified',priority:'normal'});
|
|
40
|
+
}
|
|
41
|
+
export function ideQueueContext(state) {
|
|
42
|
+
const value = state.queue;
|
|
43
|
+
if (!value) return '';
|
|
44
|
+
const pending = value.entries.filter(item=>item.status!=='completed');
|
|
45
|
+
return `${value.parents.length ? `有 ${value.parents.length} 个原任务等待恢复。` : ''}${pending.length ? `尚有 ${pending.length} 条插入需求;先用 queue status 读取,再用 queue route 分类,不能忽略或提前结束。` : ''}`;
|
|
46
|
+
}
|
|
47
|
+
export function unclassifiedIdeRequirements(state) { return state.queue?.entries.some(item=>item.status==='unclassified') ?? false; }
|
|
48
|
+
function activate(state,entry,pause) {
|
|
49
|
+
const value = ensureCurrent(state);
|
|
50
|
+
if (state.pendingCall) fail('An unresolved original call cannot be interrupted; recover it with GET first');
|
|
51
|
+
if (pause && value.current) value.parents.push({item:structuredClone(value.current),context:context(state)});
|
|
52
|
+
value.entries = value.entries.filter(item=>item.messageId!==entry.messageId);
|
|
53
|
+
value.current = {...entry,status:'active'};
|
|
54
|
+
restore(state,{phase:'entry',taskGoal:entry.goal,feedbackAcknowledged:false});
|
|
55
|
+
}
|
|
56
|
+
function sorted(value,ranks) {
|
|
57
|
+
return value.entries.filter(item=>item.status==='queued').sort((a,b)=>ranks.indexOf(b.priority)-ranks.indexOf(a.priority)||a.sequence-b.sequence);
|
|
58
|
+
}
|
|
59
|
+
export async function advanceIdeQueue(state,home) {
|
|
60
|
+
const value = ensureCurrent(state), ranks = await priorities(home), candidate = sorted(value,ranks)[0];
|
|
61
|
+
if (state.phase==='ended') {
|
|
62
|
+
if (value.current) value.completed.push({...value.current,context:context(state),completedAt:new Date().toISOString()});
|
|
63
|
+
value.current = null;
|
|
64
|
+
const parent = value.parents.at(-1);
|
|
65
|
+
if (candidate && (!parent || ranks.indexOf(candidate.priority)>ranks.indexOf(parent.item.priority))) {
|
|
66
|
+
activate(state,candidate,false);
|
|
67
|
+
return `已切换队列任务 ${candidate.messageId};必须按原需求进入受控入口。`;
|
|
68
|
+
}
|
|
69
|
+
if (parent) {
|
|
70
|
+
value.parents.pop(); value.current = parent.item; restore(state,parent.context);
|
|
71
|
+
return `插入任务已结束,自动恢复原任务${state.taskNumber ? ` ${state.taskNumber}` : ''}${state.flowId ? `,flowId=${state.flowId}` : ''}。继续原 nextStep,重新验证实际快照和权限,不能重建流程。`;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
if (!state.pendingCall && !unclassifiedIdeRequirements(state) && candidate
|
|
75
|
+
&& (state.phase==='ended' || state.phase==='setup' || candidate.mode==='interrupt'
|
|
76
|
+
&& ['continuing','blocked','needs-input'].includes(state.phase))) {
|
|
77
|
+
activate(state,candidate,state.phase!=='ended' && state.phase!=='setup');
|
|
78
|
+
return `已在受控调用结束的检查点插入需求 ${candidate.messageId};完成后必须恢复原任务。`;
|
|
79
|
+
}
|
|
80
|
+
return '';
|
|
81
|
+
}
|
|
82
|
+
export function checkQueuedStart(state,call) {
|
|
83
|
+
const item = state.queue?.current;
|
|
84
|
+
if (!item || item.original || item.status!=='active') return true;
|
|
85
|
+
const goal = call.args[0]==='cloud-task' ? call.input?.goal : call.input?.input?.task?.goal;
|
|
86
|
+
return typeof goal==='string' && ideDigest(goal)===item.goalDigest;
|
|
87
|
+
}
|
|
88
|
+
export function unfinishedIdeQueue(state) {
|
|
89
|
+
return Boolean(state.queue?.parents.length || state.queue?.entries.length || state.queue?.current && state.phase!=='ended');
|
|
90
|
+
}
|
|
91
|
+
export async function runIdeQueue(input,{environment=process.env,home,cwd=process.cwd()}={}) {
|
|
92
|
+
if (environment.ENTROROVE_IDE!=='Codex' || !environment.CODEX_THREAD_ID) fail('Task queue requires its actual guarded Codex session');
|
|
93
|
+
return ideSession({session_id:environment.CODEX_THREAD_ID,cwd},async state=>{
|
|
94
|
+
const value = ensureCurrent(state);
|
|
95
|
+
if (!state.active) fail('This session has not selected Entrorove');
|
|
96
|
+
if (input.action==='route') {
|
|
97
|
+
const entry = value.entries.find(item=>item.messageId===input.messageId);
|
|
98
|
+
if (!entry || entry.status!=='unclassified' || entry.goalDigest!==ideDigest(entry.goal)) fail('Only an actual unclassified user requirement may enter the queue');
|
|
99
|
+
if (!['queued','interrupt'].includes(input.mode) || !(await priorities(home)).includes(input.priority)) fail('Use a declared queue mode and the existing priority contract');
|
|
100
|
+
entry.mode=input.mode; entry.priority=input.priority; entry.status='queued';
|
|
101
|
+
entry.routedAt=new Date().toISOString();
|
|
102
|
+
} else if (input.action!=='status') fail('Unknown task queue action');
|
|
103
|
+
const notice = await advanceIdeQueue(state,home);
|
|
104
|
+
return {schemaVersion:'entrorove.ide-queue/1.0',sessionId:state.sessionId,current:value.current,parents:value.parents.map(parent=>({item:parent.item,...parent.context})),
|
|
105
|
+
entries:value.entries,completedCount:value.completed.length,...(notice ? {notice} : {}),
|
|
106
|
+
instruction:'Use the current requirement verbatim. Keep the original flow and task IDs, follow the controlled entry, and resume parents only after a verified end acknowledgement.'};
|
|
107
|
+
},home);
|
|
108
|
+
}
|
package/manifest.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@entrorove/skill",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.39",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"entrorove": "cli.mjs"
|
|
@@ -204,6 +204,7 @@
|
|
|
204
204
|
"ide-install.mjs",
|
|
205
205
|
"ide-session.mjs",
|
|
206
206
|
"ide-results.mjs",
|
|
207
|
+
"ide-queue.mjs",
|
|
207
208
|
"local-discipline-actions.mjs",
|
|
208
209
|
"local-discipline-analysis.mjs",
|
|
209
210
|
"local-discipline-batch.mjs",
|
package/release-state.json
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": "entrorove.release-state/1.0",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"digest": "
|
|
3
|
+
"version": "0.1.39",
|
|
4
|
+
"digest": "5c83bae7a346a4a9f4592aab374b872b66289f13d8f7e95b5cf6676b365896ae",
|
|
5
5
|
"files": {
|
|
6
6
|
"ide-code-mode.mjs": "cd5318342aac23ba9719ad760b7d7b8018d31839df1bed4ed7142c2782e22def",
|
|
7
|
-
"ide-command.mjs": "
|
|
8
|
-
"ide-hook.mjs": "
|
|
9
|
-
"ide-install.mjs": "
|
|
7
|
+
"ide-command.mjs": "11e9aeaf4e6f4dff5f4522c175dde4c48fdc29cfb4e7e1cfe9935e5a6ca842a3",
|
|
8
|
+
"ide-hook.mjs": "06b2b771113c4b288d057e84eacc51406446bf55b9fab51c7bb720917d4f1cb8",
|
|
9
|
+
"ide-install.mjs": "03ceb86a56ff2c2683a2de82b4fed54af5119a817f8017ee7789330af4a0c11f",
|
|
10
10
|
"ide-session.mjs": "bbe68b077e8854af36e4514ec6747bdeff8a10513d5169e6fa111ce8f95bcb88",
|
|
11
11
|
"ide-results.mjs": "8677b062bfe44b1683ae2508ad1048b93030a0840e8ccc1fa1c5e587ab43c7a3",
|
|
12
|
+
"ide-queue.mjs": "806e716c775fd0079417027b135977c8516f47dddb0bc5ca19e32a355dab0e9e",
|
|
12
13
|
"local-discipline-actions.mjs": "2379c08d0f49c44068f8239db7e514d75885f4606b1b88a7a36e4ff19167caca",
|
|
13
14
|
"local-discipline-analysis.mjs": "ebea932438a53a157d5d07a5d68366496eee121a2c67cecfb4fdb2d5aa9763eb",
|
|
14
15
|
"local-discipline-batch.mjs": "2cc90dbae55efdee0c445da399c0a9acafa92e915344c25208ebca554fc4c9e9",
|
|
@@ -27,9 +28,9 @@
|
|
|
27
28
|
"runtime/validator-runtime-v7.0.44.d.mts": "7797edbfe2f7de24da370204dce4dfd22822a1bd80df93514fff900b98afe36f",
|
|
28
29
|
"snapshot-retention.mjs": "4f8919fe8ec49fb4c23e080a8ad2fa3a9282bca3fffe7cf55c64e89ef316697e",
|
|
29
30
|
"snapshot-cleanup-journal.mjs": "76a9684cc95b4eb076e1298d78a9dff37948b00d9607b8f546c599072d3ed331",
|
|
30
|
-
"IDE-EXECUTION.md": "
|
|
31
|
+
"IDE-EXECUTION.md": "e34acaec2a92338fe2647ccedd2234e6e632415b22efe3fd7fcab828fe7d0a9a",
|
|
31
32
|
"REUSE-DISCIPLINE.md": "295ecd41c931b00c21d009ab65ac160f0e2ab81b4000ed498d2e8e97c2dc6c63",
|
|
32
|
-
"reuse-discipline-release.mjs": "
|
|
33
|
+
"reuse-discipline-release.mjs": "2db902cd2bc909e01c0b7238e9cdfe72f8b0b696828baa9e71eba5140336e950",
|
|
33
34
|
"local-final-gate16.mjs": "b30caab08441449ad556a6e9a3cefbf91190f0b3f7370f46d2a21156375aa8e3",
|
|
34
35
|
"local-final-gate16-checks.mjs": "1dd30e9666d458d8b12ca6e4975f6f0ac8450c3ea9cbecc7401ae037f66b589a",
|
|
35
36
|
"local-final-gate16-worker.mjs": "344d64b6c1c5552b96960e0d8514a876a3dbfa9762ab9eb749038e8351727121",
|
|
@@ -71,13 +72,13 @@
|
|
|
71
72
|
"local-autocoord17-waits.mjs": "2b0a713987822548a94e1ed3a9a891532db45383797dfc5d5bc8d1e35593cf25",
|
|
72
73
|
"local-validator-isolation17.mjs": "48e94604e28b8fddd567dfb35ff60b73fd094a5803bd49f076388b7f310f92aa",
|
|
73
74
|
"local-calctool-composition17.mjs": "ac89fcb41ab0d0d9b47c1b5696945281b0894eb064820775e36a011c76314e6a",
|
|
74
|
-
"cli.mjs": "
|
|
75
|
+
"cli.mjs": "d0f98b363515cb1ee9d9033c0ef47ddf60817bfe51a954fe155f5d823a8601da",
|
|
75
76
|
"review-client.mjs": "3491eef68ed533174b31db3c53febb6519b8f088f71a9964644714a9a4c03c77",
|
|
76
77
|
"version.mjs": "1f392147d72c40af4b03b307ea607075fb815af5174fa07e254b95fe12dbf3e7",
|
|
77
|
-
"SKILL.md": "
|
|
78
|
-
"CAPABILITY-MAP.md": "
|
|
78
|
+
"SKILL.md": "71c6c38bc33f9793af880e006e3e3eda71a4ec191e3ea6856a609248e3dea20f",
|
|
79
|
+
"CAPABILITY-MAP.md": "eb9c0687260801ff7187acd329a1f418e8fded492e50c45816d5620b2a99c2d0",
|
|
79
80
|
"AGREEMENT.md": "f93a68e92c457200dd024610e65f74e478b8f60860f951da818b70561f1b9bf8",
|
|
80
|
-
"package.json": "
|
|
81
|
+
"package.json": "e6a90e4a2fb25bbe74ee58b7e6af4770743296aa68a03562dce88ecf92418161",
|
|
81
82
|
"manifest.json": "ab06e307795bc9da5add926752b6c4dbdaf18625090261a035aa356a679c504f",
|
|
82
83
|
"release.mjs": "203e367ed6d7a406937c358c87f5e5c43d2055ceedda4ca882711121a53c28ec",
|
|
83
84
|
"local-cli.mjs": "ee6319e2027689e0a4f511c8790fa6e606e520e2f3f30be5031a2e055231de4b",
|
package/releases.json
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"version": "0.1.39",
|
|
4
|
+
"date": "2026-10-14",
|
|
5
|
+
"zh": "增加受控需求队列,支持按优先级排队或在安全检查点插入;插入任务完成并回传真实确认后自动恢复原任务与原编号。",
|
|
6
|
+
"en": "Added a controlled requirement queue with priority ordering and checkpointed interruption; after verified acknowledged completion, the original task and identifiers resume automatically."
|
|
7
|
+
},
|
|
2
8
|
{
|
|
3
9
|
"version": "0.1.38",
|
|
4
10
|
"date": "2026-10-14",
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const ide = ['ide-code-mode.mjs', 'ide-command.mjs', 'ide-hook.mjs', 'ide-install.mjs', 'ide-session.mjs', 'ide-results.mjs'];
|
|
1
|
+
const ide = ['ide-code-mode.mjs', 'ide-command.mjs', 'ide-hook.mjs', 'ide-install.mjs', 'ide-session.mjs', 'ide-results.mjs', 'ide-queue.mjs'];
|
|
2
2
|
const discipline = ['local-discipline-actions.mjs', 'local-discipline-analysis.mjs', 'local-discipline-batch.mjs',
|
|
3
3
|
'local-discipline-contracts.mjs', 'local-discipline-contracts.d.mts', 'local-discipline-gate.mjs',
|
|
4
4
|
'local-discipline-native.mjs', 'local-discipline-operations.mjs', 'local-discipline-repository.mjs',
|