cli-aimlock 7.0.35 → 7.0.37
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/README.md +8 -2
- package/aimlock-chain-calls.mjs +24 -5
- package/aimlock-chain-cli.mjs +2 -1
- package/aimlock-chain-executor.mjs +28 -12
- package/aimlock-chain-model.mjs +1 -1
- package/aimlock-chain-store.mjs +2 -0
- package/aimlock-local-runner.mjs +9 -173
- package/aimlock-read-budget-renewal.mjs +198 -0
- package/aimlock-read-budget-schemas.mjs +31 -0
- package/aimlock-read-budget-state.mjs +71 -0
- package/aimlock-read-budget.mjs +127 -0
- package/aimlock-runtime.mjs +2 -2
- package/brain-client.mjs +2 -1
- package/broker-failures.mjs +68 -0
- package/broker-recovery.mjs +110 -0
- package/broker-transport-attempt.mjs +86 -0
- package/broker-transport.mjs +107 -0
- package/broker.mjs +67 -59
- package/cli.mjs +8 -0
- package/installer.mjs +23 -2
- package/package.json +10 -2
- package/skill/SKILL.md +24 -4
- package/skill/skill.json +1 -1
package/broker.mjs
CHANGED
|
@@ -2,6 +2,9 @@ import { createHash, randomUUID } from 'node:crypto'
|
|
|
2
2
|
import { constants } from 'node:fs'
|
|
3
3
|
import { lstat, open } from 'node:fs/promises'
|
|
4
4
|
import { resolve, win32 } from 'node:path'
|
|
5
|
+
import { OfficialSkillInvocationError, OfficialSkillResponseError, transportFailureCode, transportDiagnostics } from './broker-failures.mjs'
|
|
6
|
+
import { queryOfficialSkillReceipt, SKILL_RECEIPT_HEADER, SKILL_RECEIPT_SCHEMA } from './broker-recovery.mjs'
|
|
7
|
+
export { officialSkillFailureResponse, transportFailureCode } from './broker-failures.mjs'
|
|
5
8
|
|
|
6
9
|
export const LOOKUP_TIMEOUT_MS = 8000
|
|
7
10
|
export const CALL_TIMEOUT_MS = 120_000
|
|
@@ -24,20 +27,6 @@ const VALIDATION_STATES = new Set(['passed', 'failed', 'incomplete'])
|
|
|
24
27
|
const EVALUATION_SCHEMA = 'skill-automatic-evaluation/1.0'
|
|
25
28
|
const IDENTIFIER_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/
|
|
26
29
|
const REQUEST_SCHEMA_PATTERN = /^([A-Za-z0-9.-]+\.skill)\.request\/([0-9]+\.[0-9]+)$/
|
|
27
|
-
const TRANSPORT_ERROR_CODE_PATTERN = /^[A-Z][A-Z0-9_]{1,63}$/
|
|
28
|
-
const NETWORK_TRANSPORT_ERROR = 'NETWORK_TRANSPORT'
|
|
29
|
-
const SKILL_INVOCATION_ERROR = 'SKILL_INVOCATION_FAILED'
|
|
30
|
-
|
|
31
|
-
class OfficialSkillInvocationError extends Error {
|
|
32
|
-
constructor(context, operation, transportCode) {
|
|
33
|
-
super(`${context.displayName} ${operation} invocation failed: network transport ${transportCode}`)
|
|
34
|
-
this.name = 'OfficialSkillInvocationError'
|
|
35
|
-
this.code = NETWORK_TRANSPORT_ERROR
|
|
36
|
-
this.operation = operation
|
|
37
|
-
this.retryable = false
|
|
38
|
-
this.transportCode = transportCode
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
30
|
|
|
42
31
|
function asObject(value, label) {
|
|
43
32
|
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
@@ -214,12 +203,21 @@ export function authoritativeEvaluation(value, expected) {
|
|
|
214
203
|
return evaluation
|
|
215
204
|
}
|
|
216
205
|
|
|
217
|
-
async function responsePayload(response,
|
|
206
|
+
async function responsePayload(response, context, request) {
|
|
207
|
+
const label = `${context.displayName} ${request.operation} response`
|
|
208
|
+
let payload
|
|
218
209
|
try {
|
|
219
|
-
|
|
220
|
-
} catch {
|
|
221
|
-
|
|
210
|
+
payload = await response.json()
|
|
211
|
+
} catch (error) {
|
|
212
|
+
if (error instanceof SyntaxError) {
|
|
213
|
+
throw new OfficialSkillResponseError(request, 'response-parse', `${label} is not valid JSON (HTTP ${response.status})`)
|
|
214
|
+
}
|
|
215
|
+
throw new OfficialSkillInvocationError(context, request, transportFailureCode(error), 'response-body', error?.transport)
|
|
216
|
+
}
|
|
217
|
+
if (!payload || typeof payload !== 'object' || Array.isArray(payload)) {
|
|
218
|
+
throw new OfficialSkillResponseError(request, 'response-validation', `${label} must be an object`)
|
|
222
219
|
}
|
|
220
|
+
return payload
|
|
223
221
|
}
|
|
224
222
|
|
|
225
223
|
function invocationRequest(context, operation, input) {
|
|
@@ -236,43 +234,6 @@ function invocationRequest(context, operation, input) {
|
|
|
236
234
|
}
|
|
237
235
|
}
|
|
238
236
|
|
|
239
|
-
export function transportFailureCode(error) {
|
|
240
|
-
const inspected = new Set()
|
|
241
|
-
let candidate = error
|
|
242
|
-
while (candidate && typeof candidate === 'object' && !inspected.has(candidate)) {
|
|
243
|
-
inspected.add(candidate)
|
|
244
|
-
const code = typeof candidate.code === 'string' ? candidate.code.trim() : ''
|
|
245
|
-
if (TRANSPORT_ERROR_CODE_PATTERN.test(code)) return code
|
|
246
|
-
const name = typeof candidate.name === 'string' ? candidate.name.trim() : ''
|
|
247
|
-
if (name === 'AbortError' || name === 'TimeoutError') return name
|
|
248
|
-
candidate = candidate.cause
|
|
249
|
-
}
|
|
250
|
-
return 'UNKNOWN_TRANSPORT_ERROR'
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
export function officialSkillFailureResponse(error) {
|
|
254
|
-
if (error instanceof OfficialSkillInvocationError) {
|
|
255
|
-
return {
|
|
256
|
-
ok: false,
|
|
257
|
-
error: {
|
|
258
|
-
code: error.code,
|
|
259
|
-
message: error.message,
|
|
260
|
-
operation: error.operation,
|
|
261
|
-
retryable: error.retryable,
|
|
262
|
-
transportCode: error.transportCode,
|
|
263
|
-
},
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
return {
|
|
267
|
-
ok: false,
|
|
268
|
-
error: {
|
|
269
|
-
code: SKILL_INVOCATION_ERROR,
|
|
270
|
-
message: error instanceof Error ? error.message : 'Skill invocation failed',
|
|
271
|
-
retryable: false,
|
|
272
|
-
},
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
|
|
276
237
|
export async function invokeOfficialSkill(context, operation, input, dependencies) {
|
|
277
238
|
const environment = asObject(dependencies.environment, 'broker environment')
|
|
278
239
|
if (typeof dependencies.request !== 'function') {
|
|
@@ -284,17 +245,64 @@ export async function invokeOfficialSkill(context, operation, input, dependencie
|
|
|
284
245
|
try {
|
|
285
246
|
response = await dependencies.request(context.endpoint, {
|
|
286
247
|
method: 'POST',
|
|
287
|
-
headers: { 'Content-Type': 'application/json', Authorization: authorization
|
|
248
|
+
headers: { 'Content-Type': 'application/json', Authorization: authorization,
|
|
249
|
+
[SKILL_RECEIPT_HEADER]: SKILL_RECEIPT_SCHEMA },
|
|
288
250
|
body: JSON.stringify({ input: requestEnvelope }),
|
|
289
251
|
signal: AbortSignal.timeout(CALL_TIMEOUT_MS),
|
|
290
252
|
})
|
|
291
253
|
} catch (error) {
|
|
292
|
-
|
|
254
|
+
const failure = new OfficialSkillInvocationError(context, requestEnvelope, transportFailureCode(error), 'request', error?.transport)
|
|
255
|
+
return recoverTransportFailure(context, requestEnvelope, dependencies, authorization, failure)
|
|
256
|
+
}
|
|
257
|
+
let payload
|
|
258
|
+
try {
|
|
259
|
+
payload = await responsePayload(response, context, requestEnvelope)
|
|
260
|
+
} catch (error) {
|
|
261
|
+
if (!(error instanceof OfficialSkillInvocationError)) throw error
|
|
262
|
+
return recoverTransportFailure(context, requestEnvelope, dependencies, authorization, error)
|
|
293
263
|
}
|
|
294
|
-
const payload = await responsePayload(response, `${context.displayName} ${operation} response`)
|
|
295
264
|
if (!response.ok || payload.ok !== true) {
|
|
296
|
-
throw new
|
|
265
|
+
throw new OfficialSkillResponseError(requestEnvelope, 'http-response', `${context.displayName} ${operation} failed: HTTP ${response.status}`)
|
|
297
266
|
}
|
|
267
|
+
try {
|
|
268
|
+
const invocation = validateInvocationResponse(context, operation, payload, requestEnvelope)
|
|
269
|
+
const transport = transportDiagnostics(response.transport)
|
|
270
|
+
return transport ? { ...invocation, transport } : invocation
|
|
271
|
+
} catch (error) {
|
|
272
|
+
throw new OfficialSkillResponseError(requestEnvelope, 'response-validation',
|
|
273
|
+
error instanceof Error ? error.message : 'Skill response validation failed')
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
async function recoverTransportFailure(context, request, dependencies, authorization, failure) {
|
|
278
|
+
if (failure.transport?.submitted === false) throw failure
|
|
279
|
+
try {
|
|
280
|
+
const invocation = await queryOfficialSkillReceipt(context, request, dependencies, authorization,
|
|
281
|
+
(payload) => validateInvocationResponse(context, request.operation, payload, request))
|
|
282
|
+
return failure.transport ? { ...invocation, transport: failure.transport } : invocation
|
|
283
|
+
} catch (error) {
|
|
284
|
+
if (!(error instanceof OfficialSkillResponseError) || error.code !== 'SKILL_INVOCATION_UNCERTAIN') throw error
|
|
285
|
+
failure.recovery = { status: error.receiptStatus, message: error.message }
|
|
286
|
+
throw failure
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
export async function recoverOfficialSkill(context, operation, requestId, dependencies) {
|
|
291
|
+
const normalizedOperation = requiredString(operation, 'skill operation')
|
|
292
|
+
const normalizedRequestId = requiredString(requestId, 'skill requestId')
|
|
293
|
+
if (!IDENTIFIER_PATTERN.test(normalizedOperation) || !IDENTIFIER_PATTERN.test(normalizedRequestId)) {
|
|
294
|
+
throw new Error('skill recovery identity is invalid')
|
|
295
|
+
}
|
|
296
|
+
expectedResponseSchema(context.schemaVersion)
|
|
297
|
+
const environment = asObject(dependencies.environment, 'broker environment')
|
|
298
|
+
if (typeof dependencies.request !== 'function') throw new Error('broker request dependency is required')
|
|
299
|
+
const authorization = await brainClientAuthorization(context, environment, dependencies.credentialAccess)
|
|
300
|
+
const request = { schemaVersion: context.schemaVersion, requestId: normalizedRequestId, operation: normalizedOperation }
|
|
301
|
+
return queryOfficialSkillReceipt(context, request, dependencies, authorization,
|
|
302
|
+
(payload) => validateInvocationResponse(context, normalizedOperation, payload, request))
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
function validateInvocationResponse(context, operation, payload, requestEnvelope) {
|
|
298
306
|
const invocationId = payload.feedbackInvocationId
|
|
299
307
|
if (typeof invocationId !== 'string' || !INVOCATION_PATTERN.test(invocationId)) {
|
|
300
308
|
throw new Error(`${context.displayName} ${operation} response is missing a valid feedbackInvocationId`)
|
package/cli.mjs
CHANGED
|
@@ -9,6 +9,9 @@ import { CHAIN_USAGE, runChainCli } from './aimlock-chain-cli.mjs'
|
|
|
9
9
|
import { defaultUsage, dispatchOfficialSkillCli, runIntakeHandshake } from './installer.mjs'
|
|
10
10
|
import {
|
|
11
11
|
LOCAL_CAPABILITIES,
|
|
12
|
+
authorizeReadBudgetRenewal,
|
|
13
|
+
requestReadBudgetRenewal,
|
|
14
|
+
stopReadBudgetRenewal,
|
|
12
15
|
extendReadBudget,
|
|
13
16
|
guardedWriteFile,
|
|
14
17
|
initializeReadBudget,
|
|
@@ -93,6 +96,8 @@ export function aimlockUsage(context) {
|
|
|
93
96
|
const usage = defaultUsage(context)
|
|
94
97
|
if (!usage.includes(COMMON_RUN_USAGE)) throw new Error('Shared CLI run usage contract changed')
|
|
95
98
|
return usage.replace(COMMON_RUN_USAGE, AIMLOCK_RUN_USAGE) + '\n\n' + CHAIN_USAGE
|
|
99
|
+
+ '\n\nRead-time renewal: local budget-auto-renew-request <repositoryRoot> prepares one Confirm Protocol approval;'
|
|
100
|
+
+ '\nlocal budget-auto-renew activates the approved chain/scope/policy; budget-auto-renew-stop revokes or completes it.'
|
|
96
101
|
}
|
|
97
102
|
|
|
98
103
|
async function collectApplicability(input, output) {
|
|
@@ -169,6 +174,9 @@ async function runLocalOperation(operation, repositoryRoot, input) {
|
|
|
169
174
|
if (operation === 'budget-read') return readFileWithinBudget(scoped)
|
|
170
175
|
if (operation === 'budget-status') return readBudgetStatus(scoped)
|
|
171
176
|
if (operation === 'budget-extend') return extendReadBudget(scoped)
|
|
177
|
+
if (operation === 'budget-auto-renew-request') return requestReadBudgetRenewal(scoped)
|
|
178
|
+
if (operation === 'budget-auto-renew') return authorizeReadBudgetRenewal(scoped)
|
|
179
|
+
if (operation === 'budget-auto-renew-stop') return stopReadBudgetRenewal(scoped)
|
|
172
180
|
if (operation === 'gate-issue') return issueMutationPass(scoped)
|
|
173
181
|
if (operation === 'gate-verify') return verifyMutationPassFile(scoped)
|
|
174
182
|
if (operation === 'guarded-write') return guardedWriteFile(scoped)
|
package/installer.mjs
CHANGED
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
brokerCommandInput,
|
|
14
14
|
invokeCommandInput,
|
|
15
15
|
invokeOfficialSkill,
|
|
16
|
+
recoverOfficialSkill,
|
|
16
17
|
officialSkillFailureResponse,
|
|
17
18
|
} from './broker.mjs'
|
|
18
19
|
|
|
@@ -26,9 +27,12 @@ export {
|
|
|
26
27
|
callOfficialSkill,
|
|
27
28
|
invokeCommandInput,
|
|
28
29
|
invokeOfficialSkill,
|
|
30
|
+
recoverOfficialSkill,
|
|
29
31
|
officialSkillFailureResponse,
|
|
30
32
|
} from './broker.mjs'
|
|
31
33
|
|
|
34
|
+
import { createBrokerTransport } from './broker-transport.mjs'
|
|
35
|
+
|
|
32
36
|
const INSTALL_META = 'install-meta.json'
|
|
33
37
|
const BROKER_STDIN_MAX_BYTES = 1_048_576
|
|
34
38
|
|
|
@@ -91,7 +95,8 @@ export function installTarget(skillName, explicit) {
|
|
|
91
95
|
}
|
|
92
96
|
|
|
93
97
|
export async function fetchLatestVersion(context) {
|
|
94
|
-
const
|
|
98
|
+
const request = createBrokerTransport({ environment: process.env })
|
|
99
|
+
const response = await request(context.latestEndpoint, { signal: AbortSignal.timeout(LOOKUP_TIMEOUT_MS) })
|
|
95
100
|
if (!response.ok) throw new Error(`cli.tax skill lookup failed: HTTP ${response.status}`)
|
|
96
101
|
const data = asObject(await response.json(), 'cli.tax skill lookup')
|
|
97
102
|
return {
|
|
@@ -162,6 +167,8 @@ export function defaultUsage(context, extraLines) {
|
|
|
162
167
|
' Invoke through the restricted local broker; a valid real HTTP invocation submits one authority-bound evaluation.',
|
|
163
168
|
` npx ${context.npmName}@latest broker`,
|
|
164
169
|
' Read one {"operation":"...","input":{...}} request from JSON stdin.',
|
|
170
|
+
` npx ${context.npmName}@latest recover <operation> <requestId>`,
|
|
171
|
+
' Query an uncertain invocation without resending or charging again.',
|
|
165
172
|
'Credential: CLITAX_BRAIN_CLIENT_TOKEN_FILE (the broker reads it; never pass the token).',
|
|
166
173
|
`Endpoint: ${context.endpoint}`,
|
|
167
174
|
]
|
|
@@ -170,7 +177,7 @@ export function defaultUsage(context, extraLines) {
|
|
|
170
177
|
}
|
|
171
178
|
|
|
172
179
|
function brokerDependencies() {
|
|
173
|
-
return { environment: process.env, request:
|
|
180
|
+
return { environment: process.env, request: createBrokerTransport({ environment: process.env }) }
|
|
174
181
|
}
|
|
175
182
|
|
|
176
183
|
async function readBrokerSource(input) {
|
|
@@ -201,6 +208,19 @@ async function runBrokerInvocation(context, commandInput) {
|
|
|
201
208
|
}
|
|
202
209
|
}
|
|
203
210
|
|
|
211
|
+
async function runBrokerRecovery(context, args) {
|
|
212
|
+
if (args.length !== 3) throw new Error('recover requires operation and original requestId')
|
|
213
|
+
try {
|
|
214
|
+
const invocation = await recoverOfficialSkill(context, args[1], args[2], brokerDependencies())
|
|
215
|
+
console.log(JSON.stringify(invocation))
|
|
216
|
+
} catch (error) {
|
|
217
|
+
const response = officialSkillFailureResponse(error)
|
|
218
|
+
console.log(JSON.stringify({ response }))
|
|
219
|
+
console.error(response.error.message)
|
|
220
|
+
process.exitCode = 1
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
204
224
|
export async function runIntakeHandshake(context, spec) {
|
|
205
225
|
const invocation = await invokeOfficialSkill(context, 'capabilities', {}, brokerDependencies())
|
|
206
226
|
const capabilities = invocation.response
|
|
@@ -253,6 +273,7 @@ export async function dispatchOfficialSkillCli(options) {
|
|
|
253
273
|
if (command === 'install') await installOfficialSkill(context, argument)
|
|
254
274
|
else if (command === 'check') await checkOfficialSkill(context, argument)
|
|
255
275
|
else if (command === 'run') await options.runCommand(context)
|
|
276
|
+
else if (command === 'recover') await runBrokerRecovery(context, args)
|
|
256
277
|
else if (command === 'invoke') await runBrokerInvocation(context, invokeCommandInput(args))
|
|
257
278
|
else if (command === 'broker') {
|
|
258
279
|
await runBrokerInvocation(context, brokerCommandInput(await readBrokerSource(stdin)))
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"cli-aimlock": "./cli.mjs"
|
|
4
4
|
},
|
|
5
5
|
"dependencies": {
|
|
6
|
-
"cli-swarm": "7.0.
|
|
6
|
+
"cli-swarm": "7.0.37"
|
|
7
7
|
},
|
|
8
8
|
"description": "Aimlock skill installer for CLI.Tax: lock a user request into an executable aim and route Blueprint, Swarm, and Calctool.",
|
|
9
9
|
"exports": {
|
|
@@ -17,6 +17,10 @@
|
|
|
17
17
|
"cli.mjs",
|
|
18
18
|
"installer.mjs",
|
|
19
19
|
"broker.mjs",
|
|
20
|
+
"broker-failures.mjs",
|
|
21
|
+
"broker-recovery.mjs",
|
|
22
|
+
"broker-transport.mjs",
|
|
23
|
+
"broker-transport-attempt.mjs",
|
|
20
24
|
"README.md",
|
|
21
25
|
"skill/SKILL.md",
|
|
22
26
|
"skill/skill.json",
|
|
@@ -33,6 +37,10 @@
|
|
|
33
37
|
"aimlock-local-fs.mjs",
|
|
34
38
|
"aimlock-local-gate.mjs",
|
|
35
39
|
"aimlock-local-runner.mjs",
|
|
40
|
+
"aimlock-read-budget-state.mjs",
|
|
41
|
+
"aimlock-read-budget.mjs",
|
|
42
|
+
"aimlock-read-budget-renewal.mjs",
|
|
43
|
+
"aimlock-read-budget-schemas.mjs",
|
|
36
44
|
"aimlock-runtime.mjs",
|
|
37
45
|
"brain-client.mjs",
|
|
38
46
|
"brain-client-files.mjs",
|
|
@@ -45,5 +53,5 @@
|
|
|
45
53
|
"url": "https://github.com/88208555/aimlock-clitax.git"
|
|
46
54
|
},
|
|
47
55
|
"type": "module",
|
|
48
|
-
"version": "7.0.
|
|
56
|
+
"version": "7.0.37"
|
|
49
57
|
}
|
package/skill/SKILL.md
CHANGED
|
@@ -5,7 +5,7 @@ description: "Aimlock 仅用于大型、深度、跨模块、高风险、需要
|
|
|
5
5
|
|
|
6
6
|
# Aimlock Skill
|
|
7
7
|
|
|
8
|
-
Package version: v7.0.
|
|
8
|
+
Package version: v7.0.37
|
|
9
9
|
|
|
10
10
|
Endpoint: https://cli.tax/R3mQ8kWpXn
|
|
11
11
|
|
|
@@ -64,7 +64,7 @@ Only after the applicability gate activates Aimlock:
|
|
|
64
64
|
1. Call local `capabilities`, then `probe`; use only its filesystem, Git-history, package-boundary, and import-graph facts for the initial mode. Exact `targetSymbols` may resolve through a fresh ContextBase map; missing, ambiguous, or stale map entries block.
|
|
65
65
|
2. Call remote `capabilities`, `intake`, and `classify`. A fallback `bypass` response stops the Aimlock chain.
|
|
66
66
|
3. Call `scope-contract`; empty allowed paths are blocked. Initialize the mode's local read budget before exploration.
|
|
67
|
-
4. Route every source read through local `budget-read`.
|
|
67
|
+
4. Route every source read through local `budget-read`. File/token exhaustion requires `execute`, `plan`, or `blocked`, or an exact Confirm Protocol budget extension. Time may renew automatically only after the one-time task-bound approval described below.
|
|
68
68
|
5. Call `skill-route`. The server queries the current published official directory and injects only matched skills.
|
|
69
69
|
6. For Probe or Swarm, workers inspect read-only and return modification nodes. Lock stays on the current agent.
|
|
70
70
|
7. Call `propose-nodes`, `accept-nodes`, `snapshot-plan`, and `snapshot-verify` in order.
|
|
@@ -104,10 +104,24 @@ Caller-supplied full catalogs and local registry flags are forbidden. `serverRes
|
|
|
104
104
|
- `run-status`, `chain-plan`, `chain-status`
|
|
105
105
|
- `delivery-doc`, `validate-json`, `feedback`
|
|
106
106
|
|
|
107
|
-
Trusted local operations: `capabilities`, `probe`, `reassess`, `budget-init`, `budget-read`, `budget-status`, `budget-extend`, `gate-issue`, `gate-verify`, and `guarded-write`. Invoke them as `cli-aimlock local <operation> <repositoryRoot>` with JSON stdin and call local `capabilities` first for every input Schema.
|
|
107
|
+
Trusted local operations: `capabilities`, `probe`, `reassess`, `budget-init`, `budget-read`, `budget-status`, `budget-extend`, `budget-auto-renew-request`, `budget-auto-renew`, `budget-auto-renew-stop`, `gate-issue`, `gate-verify`, and `guarded-write`. Invoke them as `cli-aimlock local <operation> <repositoryRoot>` with JSON stdin and call local `capabilities` first for every input Schema.
|
|
108
108
|
|
|
109
109
|
`chain-plan` accepts only server-resolved skill IDs. High-risk work is blocked unless both Confirm Protocol and Validator were resolved. Confirm Protocol is forced to the first step; the caller must invoke the returned `confirmProtocolRequest`, then submit its authoritative `interaction-answer` response with the same `confirmationRequestId`. Replayed or mismatched approval remains blocked. When `swarm` is present, the plan inserts the internal `coordinator.conflict-scan` step immediately before it; no unrelated external skill is added.
|
|
110
110
|
|
|
111
|
+
## Long-task read-time renewal
|
|
112
|
+
|
|
113
|
+
Swarm starts with 60 minutes; Lock and Probe remain 2 and 8 minutes. Time is wall-clock elapsed since budget initialization, including waits. Renewal is evaluated on a budgeted source/cache read, never by `budget-status` or a background timer.
|
|
114
|
+
|
|
115
|
+
An expired but still authorized budget reports `autoRenewEligible: true`, `decisionRequired: false`, and `nextActions: ["budget-read"]`; continue through that read operation to apply the permitted time extension. A status query never consumes an interval.
|
|
116
|
+
|
|
117
|
+
1. Call `budget-auto-renew-request` with `chainId`, a unique `requestId`, `scope: {goal, allowedPaths}`, and `policy: {intervalMs, maxRenewals}`. Use literal repository-relative files/directories from the accepted task scope. The returned Confirm interaction states the exact task, scope, interval and total renewal cap.
|
|
118
|
+
2. Render that interaction to the user once and obtain an authoritative Confirm Protocol `interaction-answer` response. Invoke `budget-auto-renew` with the same chain/scope/policy and that `confirmation`. A generic earlier approval, remembered response, altered scope or replay does not authorize renewal.
|
|
119
|
+
3. Expired reads within that scope automatically consume the required fixed intervals up to the approved `maxRenewals`. Each interval records its reason, timestamp, count and cumulative duration. File/token limits and write permissions do not increase; exhausted quotas or renewal caps still explicitly block. Missed wall-clock intervals count toward the cap.
|
|
120
|
+
4. Call `budget-auto-renew-stop` with `reason: "revoked"` when the user revokes renewal, and `reason: "completed"` when the task finishes. Completion prohibits further reads. The local persistent chain executor also closes an existing budget when execution succeeds. Other IDE hosts must send the completion signal; this package cannot observe unrelated IDE completion automatically.
|
|
121
|
+
5. The authorization is immutable for that chain. Revocation never creates a new allowance; a later expansion requires a separate exact `budget-extend` approval, and a new task must use its own chain. Never infer permission from a long-running task or from authorization to implement this feature.
|
|
122
|
+
|
|
123
|
+
Example request input: `{"chainId":"task-42","requestId":"renew-task-42","scope":{"goal":"Complete the approved refactor","allowedPaths":["src/module"]},"policy":{"intervalMs":3600000,"maxRenewals":8}}`.
|
|
124
|
+
|
|
111
125
|
## Interrupt and keep-alive
|
|
112
126
|
|
|
113
127
|
Call `interrupt` before acting on an interruption:
|
|
@@ -132,7 +146,7 @@ Aimlock returns the protocol; it does not start a timer.
|
|
|
132
146
|
| A3 | 官方技能按需路由 | 已实现 | 服务端读取当前已发布官方目录,只注入与需求匹配的技能;不加载完整目录。 |
|
|
133
147
|
| A4 | 快照写入门禁 | 已实现(需宿主路由) | 本地运行器重读真实文件副本并签发 Ed25519 短期凭证;凭证绑定 chainId、快照摘要和路径。只有经过 `guarded-write` 的写入能被物理拦截,IDE 宿主必须关闭旁路批量写入口。 |
|
|
134
148
|
| A5 | 真实分档与逐级升级 | 已实现 | 本地读取真实路径、Git 历史、包边界和 import 图;可从新鲜 ContextBase 地图解析精确目标符号;调用方自报复杂度不能覆盖探测,升级继承现有证据。 |
|
|
135
|
-
| A6 | 读取预算与截止 | 已实现(需宿主路由) | Lock/Probe/Swarm 限制 3/10/30 文件与 2/8/
|
|
149
|
+
| A6 | 读取预算与截止 | 已实现(需宿主路由) | Lock/Probe/Swarm 限制 3/10/30 文件与 2/8/60 分钟;Probe/Swarm 另限 30K/100K 估算 token,并用进程间锁阻止并发超额。 |
|
|
136
150
|
| A7 | AutoCoord 物理联锁 | 已实现(需宿主路由) | `gate-issue` 显式选择是否需要协调;协调凭证绑定 Swarm 签名文件租约,`guarded-write` 在同一临界区校验凭证、活动锁和路径范围。活动依赖等待会阻断预算读取。 |
|
|
137
151
|
| A8 | 高风险确认联锁 | 已实现(需宿主调用) | 高风险需求自动路由 Confirm Protocol;`chain-plan` 在权威 `interaction-answer` 返回前保持阻断,并校验请求 ID、审计与回调绑定。 |
|
|
138
152
|
|
|
@@ -174,3 +188,9 @@ Aimlock returns the protocol; it does not start a timer.
|
|
|
174
188
|
4. 运行 `npx cli-aimlock@latest brain check <repositoryRoot> <handoff.json>` 执行批准的检查并回传产物哈希和结果。普通 IDE 回传属于 client-reported,不能据此声称可信验证通过。
|
|
175
189
|
5. 只有已批准的可信 runner 生成与本次计划和报告绑定的签名收据后,才运行 `npx cli-aimlock@latest brain validate <repositoryRoot> <validation.json>`。没有可信收据时保持已回传状态,不伪造验证。
|
|
176
190
|
6. 请求发送后结果不确定时,先用 `brain status <repositoryRoot> <status.json>` 按 requestId 或 planId 查询;禁止自动重发规划或重复计费。
|
|
191
|
+
|
|
192
|
+
## 网络中断与原回执恢复
|
|
193
|
+
|
|
194
|
+
仅在 TLS 握手前确定尚未发送 HTTP 请求时,broker 才允许最多 3 次连接尝试,并受总超时约束。请求发出后发生断线或响应中断,只用 GET 查询原 requestId 的服务端回执,禁止重发 POST;未取得有效回执时保留不确定状态,不得假定成功或继续依赖步骤。
|
|
195
|
+
|
|
196
|
+
`npx cli-aimlock@latest recover <operation> <requestId>` 可重新查询原调用,不会重做操作或重复计费。链恢复不会跳过人工确认,也不会自动重跑结果不确定的本地命令。代理连接需 Node.js 22.21+ 或 24.5+;不支持的运行时会明确报错。
|
package/skill/skill.json
CHANGED