@entrorove/skill 0.1.41 → 0.1.47

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.
@@ -1,13 +1,39 @@
1
- import { promises as fs } from 'node:fs';
1
+ import { constants, promises as fs } from 'node:fs';
2
+ import { createHash } from 'node:crypto';
2
3
  import path from 'node:path';
3
4
  import { canonical, sha256 } from './local-integrity.mjs';
4
5
  import { mutationRead14, mutationPath14, mutationTarget14, mutationFail14 } from './local-mutation-files.mjs';
6
+ import { budgetBindingForRead } from './local-budget-reads.mjs';
7
+ import { budgetStatus } from './local-budget.mjs';
5
8
  import { analyzeDisciplineSources } from './local-discipline-analysis.mjs';
6
9
  import { importGraph, scopeCandidateImports } from './local-scope-repository.mjs';
7
10
  import { nativeDisciplineDefinitions } from './local-discipline-native.mjs';
8
11
 
9
12
  const script = /\.(?:[cm]?[jt]sx?|php|py|java|rs|vue|svelte|css)$/i;
10
13
  const excluded = new Set(['.git', 'node_modules']);
14
+ const controlSources = new Set(['.aimlock', '.archguard', '.entrorove']);
15
+ const semanticSource = relative => script.test(relative) && !relative.split('/').some(part => controlSources.has(part));
16
+ const within = (relative, scope) => relative === scope || relative.startsWith(`${scope}/`);
17
+ function withinBudgetScope(relative, scope) {
18
+ return scope.allowedPaths.some(allowed => within(relative, allowed))
19
+ && !scope.forbiddenPaths?.some(forbidden => within(relative, forbidden));
20
+ }
21
+ const sameFile = (left, right) => ['dev', 'ino', 'size', 'mtimeMs', 'ctimeMs', 'mode'].every(key => left[key] === right[key]);
22
+ async function integrityOnlyFile(root, relative) {
23
+ // This path returns only an integrity digest. It never exposes source bytes or
24
+ // contributes semantic facts outside the signed source-read scope.
25
+ const before = await mutationTarget14(root, relative);
26
+ const handle = await fs.open(before.target, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0));
27
+ try {
28
+ const first = await handle.stat();
29
+ if (!first.isFile() || !sameFile(first, before.stat)) mutationFail14('ENTROROVE_FILE_CHANGED', `${relative} changed before integrity observation`);
30
+ const digest = createHash('sha256');
31
+ for await (const chunk of handle.createReadStream({ autoClose: false })) digest.update(chunk);
32
+ const last = await handle.stat(), current = await mutationTarget14(root, relative);
33
+ if (!sameFile(first, last) || !sameFile(last, current.stat)) mutationFail14('ENTROROVE_FILE_CHANGED', `${relative} changed during integrity observation`);
34
+ return { sha256: digest.digest('hex'), mode: last.mode & 0o777 };
35
+ } finally { await handle.close(); }
36
+ }
11
37
  async function inventory(root, prefix = '') {
12
38
  const entries = await fs.readdir(path.join(root, prefix), { withFileTypes: true }), files = [];
13
39
  for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
@@ -21,15 +47,23 @@ async function inventory(root, prefix = '') {
21
47
  }
22
48
  export async function disciplineRepositoryObservation(input, root, { analyze = false, overrides = new Map() } = {}) {
23
49
  const paths = await inventory(root), files = [], sources = [], unresolved = [];
50
+ const binding = await budgetBindingForRead(input), budget = binding ? await budgetStatus(binding) : null;
51
+ const restricted = budget?.enforced ? budget.scope : null;
52
+ if (restricted && [...overrides.keys()].some(relative => !withinBudgetScope(relative, restricted))) {
53
+ mutationFail14('ENTROROVE_BUDGET_SCOPE', 'candidate analysis is outside the original cloud task scope');
54
+ }
24
55
  for (const relative of paths) {
25
56
  const stat = await fs.lstat(path.join(root, relative));
26
57
  if (stat.isSymbolicLink()) {
27
58
  files.push({ path: relative, sha256: sha256(await fs.readlink(path.join(root, relative))), mode: stat.mode & 0o777, kind: 'symlink' });
28
59
  unresolved.push({ path: relative, reason: 'symlink-reference' }); continue;
29
60
  }
30
- const read = await mutationRead14(input, root, relative);
61
+ const semantic = analyze && semanticSource(relative);
62
+ const allowed = !restricted || withinBudgetScope(relative, restricted);
63
+ const read = semantic && allowed ? await mutationRead14(input, root, relative) : await integrityOnlyFile(root, relative);
31
64
  files.push({ path: relative, sha256: read.sha256, mode: read.mode, kind: 'file' });
32
- if (analyze && script.test(relative)) sources.push({ path: relative, content: overrides.get(relative) ?? read.content.toString('utf8') });
65
+ if (semantic && allowed) sources.push({ path: relative, content: overrides.get(relative) ?? read.content.toString('utf8') });
66
+ else if (semantic && !allowed) unresolved.push({ path: relative, reason: 'outside-signed-source-scope' });
33
67
  }
34
68
  if (canonical(paths) !== canonical(await inventory(root))) mutationFail14('ENTROROVE_DISCIPLINE_SCAN_CHANGED', 'Repository inventory changed while observing its original baseline');
35
69
  const comparison = { schemaVersion: 'entrorove.repository-comparison/1.0', files,
@@ -38,20 +72,20 @@ export async function disciplineRepositoryObservation(input, root, { analyze = f
38
72
  // The existing resolver supplies actual aliases, package exports and import edges.
39
73
  // Unsupported/dynamic references stay unknown; they are never converted to unused.
40
74
  const roots = sources.filter(file => /\.[cm]?[jt]sx?$/i.test(file.path)).map(file => ({ path: file.path, exists: true }));
41
- for (const [relative, content] of overrides) if (!paths.includes(relative) && script.test(relative)) sources.push({ path: relative, content });
75
+ for (const [relative, content] of overrides) if (!paths.includes(relative) && semanticSource(relative)) sources.push({ path: relative, content });
42
76
  let graph;
43
77
  try { graph = roots.length ? await importGraph(root, roots, input) : { edges: [], dynamicSources: [], complete: true }; }
44
78
  catch (error) {
45
- if (!['ENTROROVE_IMPORT_UNRESOLVED', 'ENTROROVE_IMPORT_INVALID', 'ENTROROVE_IMPORT_UNSUPPORTED', 'ENTROROVE_SCOPE_LIMIT'].includes(error.code)) throw error;
79
+ if (!['ENTROROVE_IMPORT_UNRESOLVED', 'ENTROROVE_IMPORT_INVALID', 'ENTROROVE_IMPORT_UNSUPPORTED', 'ENTROROVE_SCOPE_LIMIT', 'ENTROROVE_BUDGET_SCOPE'].includes(error.code)) throw error;
46
80
  graph = { edges: [], dynamicSources: [], complete: false }; unresolved.push({ reason: error.code });
47
81
  }
48
- for (const [relative, content] of overrides) if (/\.[cm]?[jt]sx?$/i.test(relative)) {
82
+ for (const [relative, content] of overrides) if (semanticSource(relative) && /\.[cm]?[jt]sx?$/i.test(relative)) {
49
83
  try {
50
84
  const candidate = await scopeCandidateImports(root, { path: relative, content }, input);
51
85
  graph.edges = [...graph.edges.filter(edge => edge.from !== relative), ...candidate.edges];
52
86
  if (candidate.dynamic) unresolved.push({ path: relative, reason: 'candidate-dynamic-import' });
53
87
  } catch (error) {
54
- if (!['ENTROROVE_IMPORT_UNRESOLVED', 'ENTROROVE_IMPORT_INVALID', 'ENTROROVE_IMPORT_UNSUPPORTED'].includes(error.code)) throw error;
88
+ if (!['ENTROROVE_IMPORT_UNRESOLVED', 'ENTROROVE_IMPORT_INVALID', 'ENTROROVE_IMPORT_UNSUPPORTED', 'ENTROROVE_BUDGET_SCOPE'].includes(error.code)) throw error;
55
89
  unresolved.push({ path: relative, reason: error.code });
56
90
  }
57
91
  }
@@ -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) {
@@ -1,6 +1,6 @@
1
1
  import { canonical, sha256 } from './local-integrity.mjs';
2
2
  import { disciplineRepositoryObservation, disciplineRepositoryDifferences } from './local-discipline-repository.mjs';
3
- import { atomicMutationJson14, privateMutationDirectory14, mutationStateRoot14 } from './local-mutation-files.mjs';
3
+ import { atomicMutationJson14, privateMutationDirectory14, mutationStateRoot14, readMutationJson14 } from './local-mutation-files.mjs';
4
4
  import path from 'node:path';
5
5
 
6
6
  export async function runnerDisciplineBefore(input, authority) {
@@ -17,7 +17,11 @@ export async function runnerDisciplineAfter(before, authority) {
17
17
  changedCount: changed.length, changedPaths: changed.slice(0, 256), gates: { repositoryUnchanged: changed.length === 0 } };
18
18
  const reportDigest = sha256(canonical(report)), state = await mutationStateRoot14(before.input);
19
19
  const directory = await privateMutationDirectory14(path.join(state, 'discipline-reports'));
20
- await atomicMutationJson14(path.join(directory.target, `${reportDigest}.json`), { ...report, reportDigest,
21
- original: before.original, actual: after }, { replace: true });
20
+ const file = path.join(directory.target, `${reportDigest}.json`);
21
+ const record = { ...report, reportDigest, original: before.original, actual: after };
22
+ try { await atomicMutationJson14(file, record); }
23
+ catch (error) {
24
+ if (error.code !== 'ENTROROVE_STATE_EXISTS' || canonical(await readMutationJson14(file)) !== canonical(record)) throw error;
25
+ }
22
26
  return { ...report, reportDigest };
23
27
  }
@@ -0,0 +1,92 @@
1
+ // Delivery-boundary and stop-loss contract (额度可见性与可交付边界 · WP-C).
2
+ //
3
+ // A boundary is the only legal stopping point: it exists where the system is
4
+ // runnable, demonstrable and roll-backable. Stopping at a boundary yields the
5
+ // independent terminal status `delivered-stage-N`; it must never be reported as
6
+ // failed or blocked, and resuming continues at the next boundary without
7
+ // re-running delivered stages.
8
+ //
9
+ // Consumption is measured, never predicted: without an enforced budget there is
10
+ // no baseline, so the decision stays explicitly not-estimable instead of
11
+ // inventing a number.
12
+ const terminalPattern = /^delivered-stage-([1-9][0-9]{0,2})$/u;
13
+ export const STAGE_BOUNDARY_SCHEMA = 'entrorove.stage-boundary/1.0';
14
+
15
+ export function stageTerminal(count) {
16
+ if (!Number.isSafeInteger(count) || count < 1 || count > 999) throw new Error('A delivered stage count requires a positive bounded integer');
17
+ return `delivered-stage-${count}`;
18
+ }
19
+
20
+ export function stageOf(status) {
21
+ const match = typeof status === 'string' ? terminalPattern.exec(status) : null;
22
+ return match ? Number(match[1]) : null;
23
+ }
24
+
25
+ export function isStageTerminal(status) { return stageOf(status) !== null; }
26
+
27
+ export function stageMessage(status, remaining) {
28
+ const stage = stageOf(status);
29
+ if (stage === null) throw new Error('A delivered stage message requires a delivered terminal status');
30
+ if (!Number.isSafeInteger(remaining) || remaining < 0) throw new Error('Delivered stages require their remaining bounded count');
31
+ return remaining === 0
32
+ ? `阶段 ${stage} 已交付,全部阶段完成。`
33
+ : `阶段 ${stage} 已交付,剩余 ${remaining} 个阶段待续跑;从下一个边界继续,不重新执行已交付阶段。`;
34
+ }
35
+
36
+ function measuredMetering(metering) {
37
+ if (!metering || typeof metering !== 'object' || Array.isArray(metering)) throw new Error('Boundary metering requires the actual consumption view');
38
+ const { enforced, elapsedMs, maxDurationMs } = metering;
39
+ if (typeof enforced !== 'boolean') throw new Error('Boundary metering requires its enforcement flag');
40
+ if (!enforced) return null;
41
+ if (!Number.isSafeInteger(elapsedMs) || elapsedMs < 0 || !Number.isSafeInteger(maxDurationMs) || maxDurationMs < 1) {
42
+ throw new Error('Enforced boundary metering requires measured elapsed and bounded duration');
43
+ }
44
+ return { elapsedMs, maxDurationMs };
45
+ }
46
+
47
+ // The decision is taken at a clean boundary: close here when the measured
48
+ // consumption of the stage just delivered reaches the configured share of the
49
+ // enforced budget, otherwise continue to the next boundary.
50
+ export function boundaryDecision(input) {
51
+ const { steps, deliveredSequence, metering, ratio = 0.8 } = input ?? {};
52
+ if (!Array.isArray(steps) || steps.length === 0 || steps.some(step => !step || !Number.isSafeInteger(step.sequence))) {
53
+ throw new Error('Boundary decision requires the accepted plan steps with their sequences');
54
+ }
55
+ if (!Number.isSafeInteger(deliveredSequence) || deliveredSequence < 0 || deliveredSequence >= steps.length) {
56
+ throw new Error('Boundary decision requires a delivered sequence inside the accepted plan');
57
+ }
58
+ if (typeof ratio !== 'number' || !(ratio > 0 && ratio <= 1)) throw new Error('Boundary threshold ratio must be inside (0,1]');
59
+ const remainingStages = steps.length - deliveredSequence;
60
+ const nextSequence = deliveredSequence + 1;
61
+ const measured = measuredMetering(metering);
62
+ if (!measured) {
63
+ return { schemaVersion: STAGE_BOUNDARY_SCHEMA, action: 'continue', status: null, nextSequence, remainingStages,
64
+ reason: 'not-estimable', basis: 'none', detail: '没有强制预算基线,不预估剩余量;继续到下一个边界。' };
65
+ }
66
+ const consumed = measured.elapsedMs / measured.maxDurationMs;
67
+ const reached = consumed >= ratio;
68
+ return { schemaVersion: STAGE_BOUNDARY_SCHEMA, action: reached ? 'close' : 'continue',
69
+ status: reached ? stageTerminal(deliveredSequence) : null, nextSequence, remainingStages,
70
+ reason: reached ? 'near-threshold' : 'below-threshold', basis: 'measured',
71
+ consumedMs: measured.elapsedMs, budgetMs: measured.maxDurationMs, ratio,
72
+ detail: reached ? '已消耗达到阈值的既定比例,在当前干净边界收口。' : '仍在阈值以内,继续到下一个边界。' };
73
+ }
74
+
75
+ // Resuming starts at the first stage that was not delivered; delivered stages
76
+ // are never re-executed.
77
+ export function nextBoundary(steps, deliveredSequence) {
78
+ if (!Array.isArray(steps)) throw new Error('Resuming a boundary requires the accepted plan steps');
79
+ if (!Number.isSafeInteger(deliveredSequence) || deliveredSequence < 0) throw new Error('Resuming a boundary requires a delivered sequence');
80
+ return steps.find(step => step.sequence === deliveredSequence + 1) ?? null;
81
+ }
82
+
83
+ export function stageReport(status, steps) {
84
+ const delivered = stageOf(status);
85
+ if (delivered === null || !Array.isArray(steps)) throw new Error('A stage report requires a delivered terminal status and the accepted plan steps');
86
+ const next = nextBoundary(steps, delivered);
87
+ return { schemaVersion: STAGE_BOUNDARY_SCHEMA, status, deliveredStages: delivered, totalStages: steps.length,
88
+ remainingStages: Math.max(0, steps.length - delivered),
89
+ deliveredStepIds: steps.filter(step => step.sequence <= delivered).map(step => step.stepId ?? null),
90
+ nextStepId: next?.stepId ?? null, terminal: true,
91
+ message: stageMessage(status, Math.max(0, steps.length - delivered)) };
92
+ }
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.41",
4
+ "version": "0.1.47",
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.41",
3
+ "version": "0.1.47",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "entrorove": "cli.mjs"
@@ -14,6 +14,7 @@
14
14
  "local-contracts.mjs",
15
15
  "local-contract-types.mjs",
16
16
  "local-state-lock.mjs",
17
+ "local-stage-boundary.mjs",
17
18
  "local-execution.mjs",
18
19
  "local-swarm-authority.mjs",
19
20
  "local-swarm-baseline.mjs",
@@ -203,6 +204,7 @@
203
204
  "ide-command.mjs",
204
205
  "ide-hook.mjs",
205
206
  "ide-install.mjs",
207
+ "install-cleanup.mjs",
206
208
  "ide-session.mjs",
207
209
  "break-glass.mjs",
208
210
  "ide-results.mjs",
@@ -226,6 +228,7 @@
226
228
  "runtime/validator-runtime-v7.0.44.d.mts",
227
229
  "IDE-EXECUTION.md",
228
230
  "REUSE-DISCIPLINE.md",
231
+ "DELIVERY-BOUNDARIES.md",
229
232
  "reuse-discipline-release.mjs"
230
233
  ],
231
234
  "scripts": {
@@ -1,14 +1,14 @@
1
1
  {
2
2
  "schemaVersion": "entrorove.release-state/1.0",
3
- "version": "0.1.41",
4
- "digest": "c69d8d891d666165a31daadaf5b8e4a12fb823400a06ed0b41ff7f8e6644d2b3",
3
+ "version": "0.1.47",
4
+ "digest": "479d681e00143de8b7ac8191a245dd1240918b9ff5c0fcca2145b0a73a184452",
5
5
  "files": {
6
- "ide-code-mode.mjs": "cd5318342aac23ba9719ad760b7d7b8018d31839df1bed4ed7142c2782e22def",
7
- "ide-command.mjs": "e50a7e0b68bfc1812568f60562598a463ae169984a1abe9d401229c2b66c78c2",
8
- "ide-hook.mjs": "567593a4c06911889954dc6499bd22be0ab67c78f0d8d46ad671bf55934f9041",
9
- "ide-install.mjs": "90ff0b8d1ce26cfeae08cd88aef49e4784c0816dfdb81046bfcf463ffccb4bd8",
10
- "ide-session.mjs": "964c5088454a9069fbf5672d2456ba136718c3b2e9eec31c96f7c5943972731b",
11
- "break-glass.mjs": "2ad6f084c788d63fec11e64412864ac715bcb1a282dd2d8f03dc14e89e82135e",
6
+ "ide-code-mode.mjs": "f3f53357ba09727b3b597851976a24de93287c0faca5052448277d0904bb61f7",
7
+ "ide-command.mjs": "158d8f26c1dc28a04284e429150a2060d7278661f656a75e7e9f7600f536b7bf",
8
+ "ide-hook.mjs": "2f00e56de1e1149f117714d5dd334196f1a3f206c3bfab541393bc0f625e0108",
9
+ "ide-install.mjs": "10247efadcfeef5da001022d12080bd0f789e67fdf7862bcc65db73dda3bbe57",
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",
@@ -19,9 +19,9 @@
19
19
  "local-discipline-gate.mjs": "04b1ba092d7f4342a0e77cfbeb44d9634dbce9fec0c79e6a1e4b11a662d904ac",
20
20
  "local-discipline-native.mjs": "8b99b5ce951cab3c1426428ad807650c61c0ffb2c346d747b73b99057b382049",
21
21
  "local-discipline-operations.mjs": "9fcb7b8889b7718d42e1d37936485d8198d5017d405e0cdbbabdbbd12fcc9d48",
22
- "local-discipline-repository.mjs": "a5f894ec7551ff084fd477eaabff3391fbd7d295df54889649d540f4d64f9a47",
22
+ "local-discipline-repository.mjs": "d818aa6137459ef200892479cc8c47fe82c884dc0e76dc7e6d45a6b41a13c6ec",
23
23
  "local-discipline-snapshot.mjs": "8fd35b2cfbece8f64579f144f589a66dec9b6022d50fe931c4c1f886cb83134d",
24
- "local-runner-discipline.mjs": "44694f4444e039d5440805eb051bb4b2b8e2c2412f9e66c2f35f965a16206c3c",
24
+ "local-runner-discipline.mjs": "86f451de2f1e12a24f5596a29828891c1eba13cd4714ed0c18a77d66cecef934",
25
25
  "local-integrity.d.mts": "21357f306234bd841c0d3d90bf2d09f7663f63746f897b9f38190428ae8198b3",
26
26
  "local-equivalence-runner.mjs": "df2f406c693c4da7bb30542ec7b4d46a9336f56e9fdd263196c0f3a73d2f6e91",
27
27
  "validator-runtime-build.mjs": "da20bd19eb088d79d62e33969b4c075dc2199761b395e40060227f8f86b9519a",
@@ -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": "fe1c65819749fdd7cad4663eab7d11e6e566ef623dc30352e47effe0bd1263d9",
32
+ "IDE-EXECUTION.md": "fc40040262f07d6bcc64fe157e200bfb85e6ea1845109e4624e7903745877891",
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,16 +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": "12e2fa1e1f6b922f249668a18eec50e56c3c71d318eb5aeac6a3d63279e60d68",
76
+ "cli.mjs": "0119f5c5647c41ba5a4b709664b529be76f1a9b698ab9574ba38e6683eff9717",
77
+ "install-cleanup.mjs": "72bb6bd55382449a986828cd1ae3e98e5fd0bf373f89bbdcc039023ae72a2337",
77
78
  "review-client.mjs": "3491eef68ed533174b31db3c53febb6519b8f088f71a9964644714a9a4c03c77",
78
79
  "version.mjs": "1f392147d72c40af4b03b307ea607075fb815af5174fa07e254b95fe12dbf3e7",
79
- "SKILL.md": "d89c2a83a080a217d67d75fa0251b0f054b92e81a9e40a6c0b52c7ecf3d79099",
80
+ "SKILL.md": "6d55d552c31ee30795aec5e29b5f3eb0a007b8da9a81431901fae4b20b3f4be5",
80
81
  "CAPABILITY-MAP.md": "a0f0468a098725ced52a96dd8e22bfc7d1ce7681a8446ebf7dae3696c76c3385",
81
82
  "AGREEMENT.md": "f93a68e92c457200dd024610e65f74e478b8f60860f951da818b70561f1b9bf8",
82
- "package.json": "a522540d3967d9348a37c6538c1abb39fe6d50872aa8f7f90b85e920590091c7",
83
+ "DELIVERY-BOUNDARIES.md": "d1bb2895e23fc14bf1c76707bd1f6f03b39695501a78a0b7289dc37b099889dc",
84
+ "package.json": "8be823b5756da9b393af966a4152bee301cb49f988823aaf757e8c51bf867085",
83
85
  "manifest.json": "ab06e307795bc9da5add926752b6c4dbdaf18625090261a035aa356a679c504f",
84
- "release.mjs": "5a84da222a179cdc3b6be5db2a4a1c48085de27dff378b7598eb725f0627c98b",
85
- "local-cli.mjs": "aa4c217c5029408020f76d6c0c5dafc9a0f51216085b2624f3b6aca3cffbe555",
86
+ "release.mjs": "34a3da868e8b5b586b318f2e8275f2f238d48a20afceb7d51c0f783a120f6973",
87
+ "local-cli.mjs": "11461e4cf4f6a8612ff275bf8296ec57ff22281156cb2c72834d3c764ba422a1",
86
88
  "local-state-lock.mjs": "1dbf64264642a0851faf043e07ee9f66d2ad0508e9f799b0626246e42fae893e",
87
89
  "local-execution.mjs": "46fddd78cd954fb080c660affe90cdaa716695580427336acb820089441a8025",
88
90
  "local-integrity.mjs": "e6d6eace80c610f3c7299d4f8644b0dc023aa9e3d8d64b26535ea7fbced248f1",
@@ -93,7 +95,7 @@
93
95
  "blueprint-criterion-wire.mjs": "fb6b7dcce0b97a699e3e8968372767a3fddd83fedc750998573f789177243072",
94
96
  "blueprint-criterion-runner.mjs": "761c0e670b4b20c6bed53e5ca5d0178759505c3977ea2ed1b7223383c534bc4c",
95
97
  "local-chain-bindings.mjs": "b8eff999ccd17b53c614a3c9f5376da78fcaa8632bb3e07bc1adcceda2e75f6c",
96
- "local-chain-broker.mjs": "0f43ae3d5e713a344d94c3f67891b0d5f6ed0b70a8d237a54d06c0fa36e4c6d8",
98
+ "local-chain-broker.mjs": "d9a63f2c9eec561853a84ab5fdb7d6794f256582126017c54e5d21d0d370b015",
97
99
  "local-chain-contracts.mjs": "d4d2be4e5c6980f2dda8ff598c7333a75e0bbf2f656aa7fe327af7c9fe73da07",
98
100
  "local-chain-controller.mjs": "dd80d8001a11d8433b615a017a34c30dd130ff5cd9c5b58731314a8f93c9571b",
99
101
  "local-chain-human.mjs": "9d5dbaef13ebbbe0cfdd1146cd9c48cefcca30f33ccc0b1566e67cbd730d3499",
@@ -114,8 +116,9 @@
114
116
  "local-budget-source-bindings.mjs": "cfe97b257fd2da0dd45f95f5e45be20baa6445d11ac6d03538a0105b4985b8ff",
115
117
  "local-budget-store.mjs": "2ae36c977ecd32d81f88e2eaa697ce8c228cb4405ecc5c4169cdda104c8ce138",
116
118
  "local-budget-transition.mjs": "3894f05d4b5d4d6e16e3f201ff8902e6311cf6434010262934e0d1251aa8b9f0",
117
- "local-budget.mjs": "e4ba1b4873a7625f9ff3c43e932b7289e7f95468c9dd88c2aeddd1242d7ea415",
119
+ "local-budget.mjs": "857bf879addbfbc13bd95d0957b223e05e73a69b8092f1d1933f09ff7968659a",
118
120
  "local-flow-lifecycle.mjs": "47534e16599002e5e7cf7d3e95195f4d7a364d2b651ec339d8c6c017d71c1b05",
121
+ "local-stage-boundary.mjs": "84c3bb5beab29306274c5ca8a78f19a83ed664ae1b93a8372bb493158b304fe3",
119
122
  "local-scope-candidate.mjs": "8f0bd9606e70d4a0ebacdd3cd600ec3afed85e2bb3e1760aa1144cf55d4e028a",
120
123
  "local-scope-context.mjs": "79753abfc6c76089d17f003a1be537dfaea10d6731c542f7fc53f2a9996ed164",
121
124
  "local-scope-files.mjs": "96c301f2a45e5fa22f0b11fb15e5e564f118e53187fc7749263c78d5ed87b056",
@@ -149,7 +152,7 @@
149
152
  "local-scope-state.mjs": "8b600a91fc0068d7a8c0bb752ddba7ed30ada93daafb118d6cba2368fd94cb27",
150
153
  "local-scope-workspaces.mjs": "a44c34537e5ed2dcaf193a985e5bc05957989fbb160a650f9e37ba7d66eb2de8",
151
154
  "local-task-routing.mjs": "6e2e506b2fb0e2e8d8c5147d7df32849b57a4e94a23634f01a355d45735858e4",
152
- "remote-client.mjs": "9e4ef758fb7d83e30e82dca68623740e7848cd5a527b7a90846e125d00df0a12",
155
+ "remote-client.mjs": "bfb58132759a4312adc8ab33115d807c5a8d90c955aca3678e361ab9cefa84bd",
153
156
  "cloud-task-client.mjs": "11acb1a033c94cdfbdfb177a3402ad97f6a5226f577138c8cf6395dc3ebb1a39",
154
157
  "scope-runner-client.mjs": "646ab4f80d4a8209e922fd45f40efd968f0744d9dfc03188b222c1a2e11e83b8",
155
158
  "native-client.mjs": "6f5575909bae9daba643287f876ffa0f9ff1c53a367b32a40145d740456d6605",
@@ -159,7 +162,7 @@
159
162
  "cloud-event-stream.mjs": "e0eec9fc3e0613094523929634f25030df5ca5c5b5fe2c1e1a4586ab10574808",
160
163
  "ide-presentation.mjs": "3bac21bfc824fcdad63aafb0d7c3326cd6456923f4224106dd5c38682060f315",
161
164
  "automatic-feedback-client.mjs": "75e92f50d6fbfa4f482af36e16c9ce5dd323a02e99eb9b434dcfa3faa846a532",
162
- "task-entry-client.mjs": "1ebbbfd26c978e7cb291ab4930974bb86d1fb56f1a702808b8dc348e2cdf69f1",
165
+ "task-entry-client.mjs": "869f234402c4149eadf1ec138705fc140e2fa16b2a3ea5dc2b2f51dfb189d7de",
163
166
  "task-identity.mjs": "e0e6cf1f48bfa5ee3c5cb86a3c21adfce6d66b4220f4a164ca099263c7a09fb4",
164
167
  "task-identity.d.mts": "55472e1ffc81b9d5e5c5bd56c0bf1d18ecde03ee5170a58a15ce9caf222d8761",
165
168
  "remote-transport.mjs": "93caaca646fca298a4e08142678b134ecc3a657e00de5b9939e2364b2db9d6c4",
@@ -177,7 +180,7 @@
177
180
  "local-task-handoff-write-archive.mjs": "3668f39d8ab5664ed717994cf1b4bcc7ea23e146a0e06291f851d30337faeb77",
178
181
  "local-task-handoff-write-receipts.mjs": "e1321127bd6fcad7840e185639f81ae68a8722a0ee4264a7b504a02b27887647",
179
182
  "workflow-local-runner.mjs": "9fa899a11da2b6b7a436bf9b561641856adcbc5f8b914f6ba89a64dae224900d",
180
- "workflow-local-contracts.mjs": "604afa9e97c6dfc3d07b01509ef2476887a2fc219c9e827bcac5d563ab4719e7",
183
+ "workflow-local-contracts.mjs": "bc52a461727b34c72daa058e1aa77d58206eafad14a93a347537f9969ae51184",
181
184
  "workflow-local-client.mjs": "45c954f694d96bf0ea87ee2dc6902a6763dac2cb1df713b6a5e749632054e9af",
182
185
  "workflow-local-request.mjs": "4e24c543ce386292a87dc9cd41f90d23375f12079cacffc515c8cd01fd571780",
183
186
  "task-message-entry.mjs": "31d10056cb905237cd5649e8eb067009ff1612ec5dc1061e0373209fbc0b646a",
package/release.mjs CHANGED
@@ -48,7 +48,7 @@ const sourceFiles = [
48
48
  'local-merge-transaction.mjs',
49
49
  ...swarmSourceFiles,
50
50
  ...coordination17SourceFiles,
51
- 'cli.mjs', 'review-client.mjs', 'version.mjs', 'SKILL.md', 'CAPABILITY-MAP.md', 'AGREEMENT.md',
51
+ 'cli.mjs', 'install-cleanup.mjs', 'review-client.mjs', 'version.mjs', 'SKILL.md', 'CAPABILITY-MAP.md', 'AGREEMENT.md', 'DELIVERY-BOUNDARIES.md',
52
52
  'package.json', 'manifest.json', 'release.mjs',
53
53
  'local-cli.mjs', 'local-state-lock.mjs', 'local-execution.mjs', 'local-integrity.mjs', 'local-evidence-verification.mjs', 'local-formula.mjs', 'local-ledger.mjs',
54
54
  'local-error.mjs',
@@ -60,7 +60,7 @@ const sourceFiles = [
60
60
  'local-budget-grants.mjs', 'local-budget-handoff.mjs', 'local-budget-handoff-contracts.mjs', 'local-budget-lock.mjs',
61
61
  'local-budget-handoff-completion.mjs',
62
62
  'local-budget-reads.mjs', 'local-budget-source-bindings.mjs', 'local-budget-store.mjs',
63
- 'local-budget-transition.mjs', 'local-budget.mjs', 'local-flow-lifecycle.mjs',
63
+ 'local-budget-transition.mjs', 'local-budget.mjs', 'local-flow-lifecycle.mjs', 'local-stage-boundary.mjs',
64
64
  'local-scope-candidate.mjs',
65
65
  'local-scope-context.mjs',
66
66
  'local-scope-files.mjs',
@@ -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,9 +295,16 @@ 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' },
304
+ 'install-cleanup.mjs': { zh: '旧版受管安装清理', en: 'managed installation cleanup' },
302
305
  'version.mjs': { zh: '版本安全检查', en: 'version safety checks' },
303
306
  'SKILL.md': { zh: 'IDE 工作流程说明', en: 'IDE workflow instructions' },
307
+ 'DELIVERY-BOUNDARIES.md': { zh: '任务交付边界说明', en: 'task delivery boundaries' },
304
308
  'CAPABILITY-MAP.md': { zh: '能力覆盖映射', en: 'capability coverage map' },
305
309
  'AGREEMENT.md': { zh: '使用、隐私与推广告知', en: 'usage, privacy, and promotion notice' },
306
310
  'package.json': { zh: '安装包配置', en: 'package configuration' },
@@ -329,6 +333,7 @@ function releaseLabels() {
329
333
  'local-budget-transition.mjs': { zh: '候选范围与正式范围预算转换', en: 'candidate and accepted scope budget transition' },
330
334
  'local-budget.mjs': { zh: '本地三轴读取预算', en: 'local three-axis read budgets' },
331
335
  'local-flow-lifecycle.mjs': { zh: 'Flow 终止与任务解绑', en: 'flow termination and task unbinding' },
336
+ 'local-stage-boundary.mjs': { zh: '本地阶段边界与可恢复停止', en: 'local stage boundaries and recoverable stops' },
332
337
  'local-probe.mjs': { zh: '范围探测与分档', en: 'scope probing and tiering' },
333
338
  'local-scope-candidate.mjs': { zh: '临时接管范围独立验签', en: 'temporary takeover scope verification' },
334
339
  'local-scope-context.mjs': { zh: '精确符号范围绑定', en: 'exact symbol scope binding' },
@@ -400,6 +405,11 @@ const metadata = JSON.parse(await read('package.json'))
400
405
  const manifest = JSON.parse(await read('manifest.json'))
401
406
  const state = JSON.parse(await read(stateFile))
402
407
  const skill = await read('SKILL.md')
408
+ const generatedMetadata = new Set([stateFile, changelogFile, releasesFile])
409
+ const untrackedPackageFiles = metadata.files.filter(name => !generatedMetadata.has(name) && !sourceFiles.includes(name))
410
+ if (untrackedPackageFiles.length) {
411
+ throw new Error(`Packaged skill files lack release tracking: ${untrackedPackageFiles.join(', ')}`)
412
+ }
403
413
  if (metadata.version !== manifest.version || metadata.version !== state.version
404
414
  || !skill.includes(`Version: ${metadata.version}.`)) {
405
415
  throw new Error('Skill package, manifest, instruction, and release state versions differ')
package/releases.json CHANGED
@@ -1,4 +1,40 @@
1
1
  [
2
+ {
3
+ "version": "0.1.47",
4
+ "date": "2026-10-16",
5
+ "zh": "更新了Codex 后续轮次工具门禁、IDE 执行与复用纪律文档、技能运行和受管安装、IDE 工作流程说明、本地执行命令、远端恢复客户端。",
6
+ "en": "Updated Codex tool guards across turns, IDE execution and reuse discipline documentation, skill runtime and managed installation, IDE workflow instructions, local execution command, remote recovery client."
7
+ },
8
+ {
9
+ "version": "0.1.46",
10
+ "date": "2026-10-16",
11
+ "zh": "更新了Codex 后续轮次工具门禁、IDE 执行与复用纪律文档、技能运行和受管安装、IDE 工作流程说明。",
12
+ "en": "Updated Codex tool guards across turns, IDE execution and reuse discipline documentation, skill runtime and managed installation, IDE workflow instructions."
13
+ },
14
+ {
15
+ "version": "0.1.45",
16
+ "date": "2026-10-16",
17
+ "zh": "更新了原链同流程技能调用。",
18
+ "en": "Updated original-chain skill calls on the same workflow."
19
+ },
20
+ {
21
+ "version": "0.1.44",
22
+ "date": "2026-10-16",
23
+ "zh": "更新了Calctool 审计测试运维真实检查与独立最终闸门、自动版本与更新日志。",
24
+ "en": "Updated Calctool audit, test, operations checks and independent final gate, automatic versioning and changelog."
25
+ },
26
+ {
27
+ "version": "0.1.43",
28
+ "date": "2026-10-16",
29
+ "zh": "更新了Codex 后续轮次工具门禁、复用与变更一致性门禁、IDE 执行与复用纪律文档、技能运行和受管安装、旧版受管安装清理、IDE 工作流程说明、任务交付边界说明、安装包配置、自动版本与更新日志、本地三轴读取预算、本地阶段边界与可恢复停止、远端恢复客户端、账号沙箱偏好与任务入口确认、工作流本地调用契约。",
30
+ "en": "Updated Codex tool guards across turns, reuse and change consistency checks, IDE execution and reuse discipline documentation, skill runtime and managed installation, managed installation cleanup, IDE workflow instructions, task delivery boundaries, package configuration, automatic versioning and changelog, local three-axis read budgets, local stage boundaries and recoverable stops, remote recovery client, account sandbox preferences and task entry confirmation, workflow local invocation contracts."
31
+ },
32
+ {
33
+ "version": "0.1.42",
34
+ "date": "2026-10-16",
35
+ "zh": "修复受管钩子空文件恢复、旧安装清理及仓库控制快照误扫。",
36
+ "en": "Restored empty managed hook configs, cleaned old installations, and excluded control snapshots from semantic scanning."
37
+ },
2
38
  {
3
39
  "version": "0.1.41",
4
40
  "date": "2026-10-15",
package/remote-client.mjs CHANGED
@@ -1,4 +1,5 @@
1
1
  import { createHash } from 'node:crypto';
2
+ import { pathToFileURL } from 'node:url';
2
3
  import Ajv from 'ajv';
3
4
  import { parseTransportResponse, requestTransport, trustedOrigin } from './remote-transport.mjs';
4
5
  import { appendInvocation } from './local-invocations.mjs';
@@ -79,10 +80,12 @@ export async function invokeRemote(data, dependencies) {
79
80
  if (['blocked','failed','incomplete'].includes(routed.status)) process.exitCode = 2;
80
81
  return routed;
81
82
  }
82
- const { response, result } = await invokeDiscovered(data, token, { home, apiOrigin });
83
+ const resolvedApiOrigin = typeof apiOrigin === 'function' ? apiOrigin : () => new URL(apiUrl).origin;
84
+ const { response, result } = await invokeDiscovered(data, token, { home, apiOrigin: resolvedApiOrigin });
83
85
  process.stdout.write(JSON.stringify(presentInvocationNotices(result,dependencies.onIdeEvent,{home})) + '\n');
84
86
  if (!response.ok || result.output.status === 'blocked' || result.output.status === 'failed'
85
87
  || result.output.status === 'incomplete') process.exitCode = 2;
88
+ return result;
86
89
  }
87
90
 
88
91
  export async function invokeRemoteResult(data, dependencies) {
@@ -233,6 +236,81 @@ export async function queryStatus(requestId, dependencies) {
233
236
  process.stdout.write(JSON.stringify(presentInvocationNotices(result,dependencies.onIdeEvent)) + '\n');
234
237
  }
235
238
 
239
+ // Host quota (WP-D): a declared host adapter may report the host's own
240
+ // remaining quota. Without one, the answer is explicitly unknown and the
241
+ // skill keeps its own measured ledger only.
242
+ export async function hostQuotaView(environment = process.env) {
243
+ const declared = String(environment.ENTROROVE_IDE_ADAPTER ?? '').split(',').map(value => value.trim()).filter(Boolean);
244
+ for (const pathname of declared) {
245
+ let adapter;
246
+ try { const module = await import(pathToFileURL(pathname).href); adapter = module.default ?? module.adapter; }
247
+ catch { return { schemaVersion: 'entrorove.host-quota/1.0', state: 'unknown', reason: 'adapter-unavailable', detail: '宿主额度接口无法加载;只按 entrorove 这本账止损。' }; }
248
+ if (typeof adapter?.hostQuota !== 'function') continue;
249
+ let value;
250
+ try { value = await adapter.hostQuota(); }
251
+ catch { return { schemaVersion: 'entrorove.host-quota/1.0', state: 'unknown', reason: 'adapter-unavailable', detail: '宿主额度接口调用失败;只按 entrorove 这本账止损。' }; }
252
+ if (value && value.state === 'measured' && Number.isSafeInteger(value.remaining) && value.remaining >= 0) {
253
+ return { schemaVersion: 'entrorove.host-quota/1.0', state: 'measured', remaining: value.remaining, basis: 'host-reported' };
254
+ }
255
+ return { schemaVersion: 'entrorove.host-quota/1.0', state: 'unknown', reason: 'adapter-unavailable', detail: '宿主额度接口未返回可用测量值;只按 entrorove 这本账止损。' };
256
+ }
257
+ return { schemaVersion: 'entrorove.host-quota/1.0', state: 'unknown', reason: 'no-host-adapter', detail: '未接入宿主额度接口;只按 entrorove 这本账止损。' };
258
+ }
259
+
260
+ async function recurringCall(pathname, method, payload, dependencies, label) {
261
+ const token = await dependencies.readCredential(dependencies.home);
262
+ const response = await requestTransport(dependencies.apiOrigin(), pathname, { method,
263
+ headers: { authorization: `Bearer ${token}`, ...(payload ? { 'content-type': 'application/json' } : {}) },
264
+ ...(payload ? { body: JSON.stringify(payload) } : {}) });
265
+ if (response.status === 204) return null;
266
+ const result = await readResponse(response, label);
267
+ if (!response.ok) throw new Error(`${label} failed: HTTP ${response.status}`);
268
+ return result;
269
+ }
270
+
271
+ export async function listRecurring(dependencies) {
272
+ process.stdout.write(JSON.stringify(await recurringCall('/api/recurring', 'GET', null, dependencies, 'recurring list')) + '\n');
273
+ }
274
+
275
+ export async function claimRecurring(claimId, dependencies) {
276
+ const claimed = await recurringCall('/api/recurring/claim', 'POST', { claimId }, dependencies, 'recurring claim');
277
+ process.stdout.write(JSON.stringify(claimed ?? { claimed: false, detail: '当前没有到期的周期任务。' }) + '\n');
278
+ }
279
+
280
+ export async function finishRecurring(runId, input, dependencies) {
281
+ if (!/^[a-f0-9-]{36}$/u.test(runId)) throw new Error('finishing a recurring run requires its runId');
282
+ process.stdout.write(JSON.stringify(await recurringCall(`/api/recurring/runs/${runId}`, 'POST', input, dependencies, 'recurring finish')) + '\n');
283
+ }
284
+
285
+ export async function readRecurringRuns(recurringId, dependencies) {
286
+ if (!/^[a-f0-9-]{36}$/u.test(recurringId)) throw new Error('recurring runs require the original recurringId');
287
+ process.stdout.write(JSON.stringify(await recurringCall(`/api/recurring/${recurringId}/runs`, 'GET', null, dependencies, 'recurring runs')) + '\n');
288
+ }
289
+
290
+ export async function queryBillingExport(dependencies) {
291
+ const token = await dependencies.readCredential(dependencies.home);
292
+ const response = await readOnlyGet(dependencies.apiOrigin(), '/api/skill/billing-export', { authorization: `Bearer ${token}` }, 'billing export');
293
+ const result = await readResponse(response, 'billing export');
294
+ if (response.status === 404) throw new Error('entrorove billing failed: HTTP 404; the reachable server does not expose /api/skill/billing-export yet (deploy the matching API version)');
295
+ if (!response.ok || result?.schemaVersion !== 'entrorove.billing-export/1.0') {
296
+ throw new Error(`entrorove billing failed: HTTP ${response.status}`);
297
+ }
298
+ process.stdout.write(JSON.stringify(result) + '\n');
299
+ }
300
+
301
+ export async function queryUsageSummary(flowId, dependencies) {
302
+ if (flowId !== undefined && !/^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u.test(flowId)) throw new Error('usage requires an original flowId when given');
303
+ const path = flowId === undefined ? '/api/skill/usage-summary' : `/api/skill/usage-summary?flowId=${encodeURIComponent(flowId)}`;
304
+ const token = await dependencies.readCredential(dependencies.home);
305
+ const response = await readOnlyGet(dependencies.apiOrigin(), path, { authorization: `Bearer ${token}` }, 'usage summary');
306
+ const result = await readResponse(response, 'usage summary');
307
+ if (response.status === 404) throw new Error('entrorove usage failed: HTTP 404; the reachable server does not expose /api/skill/usage-summary yet (deploy the matching API version)');
308
+ if (!response.ok || result?.schemaVersion !== 'entrorove.usage-summary/1.0') {
309
+ throw new Error(`entrorove usage failed: HTTP ${response.status}`);
310
+ }
311
+ process.stdout.write(JSON.stringify({ ...result, hostQuota: await hostQuotaView(dependencies.environment) }) + '\n');
312
+ }
313
+
236
314
  export async function queryFlows(dependencies) {
237
315
  const token = await dependencies.readCredential(dependencies.home);
238
316
  const response = await readOnlyGet(dependencies.apiOrigin(), '/api/skill/flows', { authorization: `Bearer ${token}` }, 'flow list')
@@ -5,6 +5,10 @@ import { displayIdeEvent, ideClientHeaders, ideTaskIdentity } from './ide-presen
5
5
  /** New IDE tasks use the authenticated account preference; resumes keep their original route. */
6
6
  export async function routeTaskEntry(data, token, dependencies) {
7
7
  if (data.operation !== 'start' || data.input?.sandboxTaskId) return null;
8
+ // Older embedders can invoke the remote client without the optional cloud
9
+ // sandbox preference callback. Keep those callers on the normal API path;
10
+ // production installs provide the callback and retain the preference gate.
11
+ if (typeof dependencies.apiOrigin !== 'function') return null;
8
12
  const choice = data.sandboxChoice;
9
13
  if (choice !== undefined && !['use','local'].includes(choice)) throw new Error('sandboxChoice must be use or local');
10
14
  if (choice === 'local') {
@@ -1,4 +1,5 @@
1
1
  import { array, enumeration, integer, ledgerIdentifier, object, relativePath, sha256 } from './local-contract-types.mjs';
2
+ import { DISCIPLINE_POLICY_SCHEMA } from './local-discipline-contracts.mjs';
2
3
 
3
4
  const exact = properties => object(properties, Object.keys(properties));
4
5
  const boundedArray = (items, minimum, maximum) => ({ ...array(items, minimum), maxItems: maximum });
@@ -17,6 +18,7 @@ const executionPolicy = exact({ executable: absolutePath, executableSha256: sha2
17
18
  additionalProperties: { type: 'string', maxLength: 16_384, pattern: '^[^\\u0000]*$' } },
18
19
  timeoutMs: integer(1, 300_000), maxOutputBytes: integer(1, 1024 * 1024), requiredExitCode: { const: 0 },
19
20
  });
21
+ executionPolicy.properties.discipline = DISCIPLINE_POLICY_SCHEMA;
20
22
  const baseline = exact({ schemaVersion: { const: 'validator.golden-baseline/1.0' }, baselineId: id,
21
23
  source: exact({ kind: enumeration(['repository-commit', 'artifact', 'approved-record']), locator: shortText, digestSha256: sha256 }),
22
24
  version: id, frozen: { const: true }, frozenAt: timestamp, frozenBy: id, testsSha256: sha256,