@phyzess/pappra 0.1.0-beta.18 → 0.1.0-beta.19
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/assets/integrations/skills/pappra/SKILL.md +29 -1
- package/assets/integrations/skills/pappra/references/collaboration.md +13 -0
- package/assets/integrations/skills/pappra/references/environment.md +7 -0
- package/dist/app/collab-session-transition-request.d.ts +50 -0
- package/dist/app/collab-session-transition-request.js +339 -0
- package/dist/app/collab-session-transition-validation.d.ts +19 -0
- package/dist/app/collab-session-transition-validation.js +44 -0
- package/dist/app/collab-session-transition.d.ts +4 -44
- package/dist/app/collab-session-transition.js +3 -367
- package/dist/cli/collaboration-router.d.ts +17 -0
- package/dist/cli/collaboration-router.js +604 -0
- package/dist/cli/options.d.ts +806 -0
- package/dist/cli/options.js +293 -0
- package/dist/cli/run.js +31 -903
- package/dist/hosts/integration/pappra-session-transition-extension.mjs +22 -22
- package/dist/hosts/pilot.d.ts +21 -21
- package/package.json +3 -1
- package/skills/pappra/SKILL.md +29 -1
- package/skills/pappra/references/collaboration.md +13 -0
- package/skills/pappra/references/environment.md +7 -0
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { object as isRecord } from '../infra/validation.js';
|
|
2
|
-
import crypto from 'node:crypto';
|
|
3
2
|
import fs from 'node:fs';
|
|
4
3
|
import path from 'node:path';
|
|
5
4
|
import { readManagedSourceAssignment } from './managed-pi-source-admission.js';
|
|
@@ -25,54 +24,22 @@ import { isTrustedManagedPiScopedProof } from './managed-pi-transition-host.js';
|
|
|
25
24
|
import { verifyManagedPiSdkInstallation } from '../hosts/pi/managed-sdk-session.js';
|
|
26
25
|
import { MANAGED_PI_EXECUTION_SCOPE_SCHEMA, } from '../schemas/managed-pi-execution.js';
|
|
27
26
|
export { MANAGED_PI_EXECUTION_SCOPE_SCHEMA };
|
|
28
|
-
export
|
|
27
|
+
export { COLLAB_SESSION_TRANSITION_REQUEST_SCHEMA, loadSessionTransitionRequest, validateSessionTransitionRequest, } from './collab-session-transition-request.js';
|
|
28
|
+
import { immutableDigest, } from './collab-session-transition-request.js';
|
|
29
|
+
import { digestText, isDigest, isNonNegativeInt, isPositiveInt, isSafeId, isText, safePathEqual, sameIdentity, } from './collab-session-transition-validation.js';
|
|
29
30
|
export const COLLAB_SESSION_TRANSITION_REPORT_SCHEMA = 'pappra/collaboration-session-transition/1';
|
|
30
31
|
export const SESSION_TRANSITION_ARCHIVE_SCHEMA = 'pappra/session-transition-archive/1';
|
|
31
|
-
const MAX_REQUEST_BYTES = 64 * 1024;
|
|
32
32
|
const MAX_JOURNAL_ENTRIES = 32;
|
|
33
33
|
const MAX_JOURNAL_BYTES = 64 * 1024;
|
|
34
34
|
const MAX_ARCHIVE_BYTES = 16 * 1024;
|
|
35
35
|
const MAX_QUIESCENCE_AGE_MS = 30_000;
|
|
36
|
-
const DIGEST = /^sha256:[0-9a-f]{64}$/;
|
|
37
|
-
const SAFE_ID = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
|
|
38
36
|
export const SESSION_TRANSITION_JOURNAL_KEY = 'session_transition_journal';
|
|
39
37
|
const JOURNAL_KEY = SESSION_TRANSITION_JOURNAL_KEY;
|
|
40
38
|
export const MANAGED_PI_SCOPED_PROOF_ADAPTER_ID = 'pappra/managed-pi-session-transition-host/2';
|
|
41
39
|
export const MANAGED_PI_SCOPED_RECEIPT_SCHEMA = 'pappra/session-transition-quiescence-receipt/2';
|
|
42
|
-
function digestText(bytes) {
|
|
43
|
-
return `sha256:${crypto.createHash('sha256').update(bytes).digest('hex')}`;
|
|
44
|
-
}
|
|
45
40
|
function failure(kind, code, message, nextAction) {
|
|
46
41
|
return { ok: false, kind, code, message, nextAction };
|
|
47
42
|
}
|
|
48
|
-
function hasOnlyKeys(value, keys) {
|
|
49
|
-
const allowed = new Set(keys);
|
|
50
|
-
return Object.keys(value).every((key) => allowed.has(key));
|
|
51
|
-
}
|
|
52
|
-
function isText(value, max = 2048) {
|
|
53
|
-
return (typeof value === 'string' &&
|
|
54
|
-
value.length > 0 &&
|
|
55
|
-
value.length <= max &&
|
|
56
|
-
!value.includes('\u0000'));
|
|
57
|
-
}
|
|
58
|
-
function isPositiveInt(value) {
|
|
59
|
-
return Number.isSafeInteger(value) && value > 0;
|
|
60
|
-
}
|
|
61
|
-
function isNonNegativeInt(value) {
|
|
62
|
-
return Number.isSafeInteger(value) && value >= 0;
|
|
63
|
-
}
|
|
64
|
-
function isDigest(value) {
|
|
65
|
-
return typeof value === 'string' && DIGEST.test(value);
|
|
66
|
-
}
|
|
67
|
-
function isSafeId(value) {
|
|
68
|
-
return typeof value === 'string' && SAFE_ID.test(value);
|
|
69
|
-
}
|
|
70
|
-
function sameIdentity(left, right) {
|
|
71
|
-
return (left !== null &&
|
|
72
|
-
left !== undefined &&
|
|
73
|
-
left.agent_id === right.agent_id &&
|
|
74
|
-
left.session_id === right.session_id);
|
|
75
|
-
}
|
|
76
43
|
function sameHarnessIdentity(left, right) {
|
|
77
44
|
if (!sameIdentity(left, right))
|
|
78
45
|
return false;
|
|
@@ -87,340 +54,9 @@ function sameHarnessIdentity(left, right) {
|
|
|
87
54
|
return false;
|
|
88
55
|
return true;
|
|
89
56
|
}
|
|
90
|
-
function safePathEqual(left, right) {
|
|
91
|
-
try {
|
|
92
|
-
return fs.realpathSync(left) === fs.realpathSync(right);
|
|
93
|
-
}
|
|
94
|
-
catch {
|
|
95
|
-
return false;
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
57
|
function nowIso(clock) {
|
|
99
58
|
return new Date(clock()).toISOString();
|
|
100
59
|
}
|
|
101
|
-
function immutableDigest(request) {
|
|
102
|
-
return digestText(canonicalJson({
|
|
103
|
-
schema: request.schema,
|
|
104
|
-
task_root: request.task_root,
|
|
105
|
-
transition_id: request.transition_id,
|
|
106
|
-
intent: request.intent,
|
|
107
|
-
bundle: request.bundle,
|
|
108
|
-
bootstrap_request: request.bootstrap_request,
|
|
109
|
-
archive_mode: request.archive_mode,
|
|
110
|
-
...(request.managed_execution_scope === undefined
|
|
111
|
-
? {}
|
|
112
|
-
: { managed_execution_scope: request.managed_execution_scope }),
|
|
113
|
-
}));
|
|
114
|
-
}
|
|
115
|
-
function parseIdentity(value) {
|
|
116
|
-
if (!isRecord(value) ||
|
|
117
|
-
!hasOnlyKeys(value, ['agent_id', 'session_id', 'harness', 'cwd']))
|
|
118
|
-
return null;
|
|
119
|
-
if (!isText(value['agent_id'], 128) || !isText(value['session_id'], 256))
|
|
120
|
-
return null;
|
|
121
|
-
if (value['harness'] !== 'pi')
|
|
122
|
-
return null;
|
|
123
|
-
if (!isText(value['cwd'], 4096) || !path.isAbsolute(value['cwd']))
|
|
124
|
-
return null;
|
|
125
|
-
return {
|
|
126
|
-
agent_id: value['agent_id'],
|
|
127
|
-
session_id: value['session_id'],
|
|
128
|
-
harness: 'pi',
|
|
129
|
-
cwd: value['cwd'],
|
|
130
|
-
};
|
|
131
|
-
}
|
|
132
|
-
function parseIntent(value) {
|
|
133
|
-
if (!isRecord(value) ||
|
|
134
|
-
!hasOnlyKeys(value, [
|
|
135
|
-
'intent_id',
|
|
136
|
-
'generation',
|
|
137
|
-
'handoff_id',
|
|
138
|
-
'subtask_id',
|
|
139
|
-
'attempt',
|
|
140
|
-
'input_revision',
|
|
141
|
-
'from',
|
|
142
|
-
'to',
|
|
143
|
-
'claims',
|
|
144
|
-
'coordinator_epoch',
|
|
145
|
-
]))
|
|
146
|
-
return null;
|
|
147
|
-
const from = parseIdentity(value['from']);
|
|
148
|
-
const to = parseIdentity(value['to']);
|
|
149
|
-
const claims = value['claims'];
|
|
150
|
-
if (!isSafeId(value['intent_id']) ||
|
|
151
|
-
!isPositiveInt(value['generation']) ||
|
|
152
|
-
!isSafeId(value['handoff_id']) ||
|
|
153
|
-
!isSafeId(value['subtask_id']) ||
|
|
154
|
-
!isPositiveInt(value['attempt']) ||
|
|
155
|
-
!isDigest(value['input_revision']) ||
|
|
156
|
-
from === null ||
|
|
157
|
-
to === null ||
|
|
158
|
-
!isPositiveInt(value['coordinator_epoch']) ||
|
|
159
|
-
!Array.isArray(claims) ||
|
|
160
|
-
claims.length === 0 ||
|
|
161
|
-
claims.length > 64 ||
|
|
162
|
-
sameIdentity(from, to))
|
|
163
|
-
return null;
|
|
164
|
-
const parsedClaims = [];
|
|
165
|
-
const seen = new Set();
|
|
166
|
-
for (const claim of claims) {
|
|
167
|
-
if (!isRecord(claim) ||
|
|
168
|
-
!hasOnlyKeys(claim, ['claim_id', 'resource_epoch']) ||
|
|
169
|
-
!isSafeId(claim['claim_id']) ||
|
|
170
|
-
!isNonNegativeInt(claim['resource_epoch']) ||
|
|
171
|
-
seen.has(claim['claim_id']))
|
|
172
|
-
return null;
|
|
173
|
-
seen.add(claim['claim_id']);
|
|
174
|
-
parsedClaims.push({
|
|
175
|
-
claim_id: claim['claim_id'],
|
|
176
|
-
resource_epoch: claim['resource_epoch'],
|
|
177
|
-
});
|
|
178
|
-
}
|
|
179
|
-
return {
|
|
180
|
-
intent_id: value['intent_id'],
|
|
181
|
-
generation: value['generation'],
|
|
182
|
-
handoff_id: value['handoff_id'],
|
|
183
|
-
subtask_id: value['subtask_id'],
|
|
184
|
-
attempt: value['attempt'],
|
|
185
|
-
input_revision: value['input_revision'],
|
|
186
|
-
from,
|
|
187
|
-
to,
|
|
188
|
-
claims: parsedClaims,
|
|
189
|
-
coordinator_epoch: value['coordinator_epoch'],
|
|
190
|
-
};
|
|
191
|
-
}
|
|
192
|
-
function parseManagedPiExecutionScope(value) {
|
|
193
|
-
if (!isRecord(value) ||
|
|
194
|
-
!hasOnlyKeys(value, [
|
|
195
|
-
'schema',
|
|
196
|
-
'task_id',
|
|
197
|
-
'task_root',
|
|
198
|
-
'session_id',
|
|
199
|
-
'cwd',
|
|
200
|
-
'subtask_id',
|
|
201
|
-
'attempt',
|
|
202
|
-
'owner_epoch',
|
|
203
|
-
'claim_epochs',
|
|
204
|
-
]) ||
|
|
205
|
-
value['schema'] !== MANAGED_PI_EXECUTION_SCOPE_SCHEMA ||
|
|
206
|
-
!isSafeId(value['task_id']) ||
|
|
207
|
-
!isText(value['task_root'], 4096) ||
|
|
208
|
-
!path.isAbsolute(value['task_root']) ||
|
|
209
|
-
!isText(value['session_id'], 256) ||
|
|
210
|
-
!isText(value['cwd'], 4096) ||
|
|
211
|
-
!path.isAbsolute(value['cwd']) ||
|
|
212
|
-
!isSafeId(value['subtask_id']) ||
|
|
213
|
-
!isPositiveInt(value['attempt']) ||
|
|
214
|
-
!isPositiveInt(value['owner_epoch']) ||
|
|
215
|
-
!Array.isArray(value['claim_epochs']) ||
|
|
216
|
-
value['claim_epochs'].length === 0 ||
|
|
217
|
-
value['claim_epochs'].length > 64)
|
|
218
|
-
return null;
|
|
219
|
-
const claim_epochs = [];
|
|
220
|
-
const seen = new Set();
|
|
221
|
-
for (const raw of value['claim_epochs']) {
|
|
222
|
-
if (!isRecord(raw) ||
|
|
223
|
-
!hasOnlyKeys(raw, ['claim_id', 'resource_epoch']) ||
|
|
224
|
-
!isSafeId(raw['claim_id']) ||
|
|
225
|
-
!isPositiveInt(raw['resource_epoch']) ||
|
|
226
|
-
seen.has(raw['claim_id']))
|
|
227
|
-
return null;
|
|
228
|
-
seen.add(raw['claim_id']);
|
|
229
|
-
claim_epochs.push({
|
|
230
|
-
claim_id: raw['claim_id'],
|
|
231
|
-
resource_epoch: raw['resource_epoch'],
|
|
232
|
-
});
|
|
233
|
-
}
|
|
234
|
-
return {
|
|
235
|
-
schema: MANAGED_PI_EXECUTION_SCOPE_SCHEMA,
|
|
236
|
-
task_id: value['task_id'],
|
|
237
|
-
task_root: value['task_root'],
|
|
238
|
-
session_id: value['session_id'],
|
|
239
|
-
cwd: value['cwd'],
|
|
240
|
-
subtask_id: value['subtask_id'],
|
|
241
|
-
attempt: value['attempt'],
|
|
242
|
-
owner_epoch: value['owner_epoch'],
|
|
243
|
-
claim_epochs,
|
|
244
|
-
};
|
|
245
|
-
}
|
|
246
|
-
export function validateSessionTransitionRequest(value) {
|
|
247
|
-
if (!isRecord(value) ||
|
|
248
|
-
!hasOnlyKeys(value, [
|
|
249
|
-
'schema',
|
|
250
|
-
'task_root',
|
|
251
|
-
'transition_id',
|
|
252
|
-
'intent',
|
|
253
|
-
'bundle',
|
|
254
|
-
'bootstrap_request',
|
|
255
|
-
'evidence',
|
|
256
|
-
'archive_mode',
|
|
257
|
-
'managed_execution_scope',
|
|
258
|
-
]))
|
|
259
|
-
return {
|
|
260
|
-
ok: false,
|
|
261
|
-
code: 'request_shape_invalid',
|
|
262
|
-
message: 'Session-transition request must use only documented fields.',
|
|
263
|
-
};
|
|
264
|
-
if (value['schema'] !== COLLAB_SESSION_TRANSITION_REQUEST_SCHEMA)
|
|
265
|
-
return {
|
|
266
|
-
ok: false,
|
|
267
|
-
code: 'request_schema_invalid',
|
|
268
|
-
message: `request.schema must be ${COLLAB_SESSION_TRANSITION_REQUEST_SCHEMA}.`,
|
|
269
|
-
};
|
|
270
|
-
if (!isText(value['task_root'], 4096) || !path.isAbsolute(value['task_root']))
|
|
271
|
-
return {
|
|
272
|
-
ok: false,
|
|
273
|
-
code: 'task_root_invalid',
|
|
274
|
-
message: 'task_root must be an absolute task-root path.',
|
|
275
|
-
};
|
|
276
|
-
const intent = parseIntent(value['intent']);
|
|
277
|
-
if (intent === null)
|
|
278
|
-
return {
|
|
279
|
-
ok: false,
|
|
280
|
-
code: 'intent_invalid',
|
|
281
|
-
message: 'intent must pin exact source/successor identities, attempt, input, epoch and claims.',
|
|
282
|
-
};
|
|
283
|
-
if (value['transition_id'] !== intent.intent_id)
|
|
284
|
-
return {
|
|
285
|
-
ok: false,
|
|
286
|
-
code: 'transition_id_mismatch',
|
|
287
|
-
message: 'transition_id must equal intent.intent_id.',
|
|
288
|
-
};
|
|
289
|
-
const bundleValue = value['bundle'];
|
|
290
|
-
if (!isRecord(bundleValue) ||
|
|
291
|
-
!hasOnlyKeys(bundleValue, [
|
|
292
|
-
'root',
|
|
293
|
-
'continuity_task_id',
|
|
294
|
-
'bundle_id',
|
|
295
|
-
'digest',
|
|
296
|
-
]) ||
|
|
297
|
-
!isText(bundleValue['root'], 4096) ||
|
|
298
|
-
!path.isAbsolute(bundleValue['root']) ||
|
|
299
|
-
!isSafeId(bundleValue['continuity_task_id']) ||
|
|
300
|
-
!isSafeId(bundleValue['bundle_id']) ||
|
|
301
|
-
!isDigest(bundleValue['digest']))
|
|
302
|
-
return {
|
|
303
|
-
ok: false,
|
|
304
|
-
code: 'bundle_invalid',
|
|
305
|
-
message: 'bundle must name one absolute workspace root and exact linked bundle digest.',
|
|
306
|
-
};
|
|
307
|
-
const bootstrapValue = value['bootstrap_request'];
|
|
308
|
-
if (!isRecord(bootstrapValue) ||
|
|
309
|
-
!hasOnlyKeys(bootstrapValue, ['ref', 'digest']) ||
|
|
310
|
-
!isText(bootstrapValue['ref'], 4096) ||
|
|
311
|
-
!path.isAbsolute(bootstrapValue['ref']) ||
|
|
312
|
-
!isDigest(bootstrapValue['digest']))
|
|
313
|
-
return {
|
|
314
|
-
ok: false,
|
|
315
|
-
code: 'bootstrap_request_invalid',
|
|
316
|
-
message: 'bootstrap_request must pin one absolute bounded request file and raw digest.',
|
|
317
|
-
};
|
|
318
|
-
const evidenceValue = value['evidence'];
|
|
319
|
-
if (!isRecord(evidenceValue) ||
|
|
320
|
-
!hasOnlyKeys(evidenceValue, ['ack_message_id', 'first_work_report_id']))
|
|
321
|
-
return {
|
|
322
|
-
ok: false,
|
|
323
|
-
code: 'evidence_invalid',
|
|
324
|
-
message: 'evidence must contain only the optional registered ACK and REPORT ids.',
|
|
325
|
-
};
|
|
326
|
-
const ack = evidenceValue['ack_message_id'];
|
|
327
|
-
const report = evidenceValue['first_work_report_id'];
|
|
328
|
-
if ((ack !== null && !isSafeId(ack)) ||
|
|
329
|
-
(report !== null && !isSafeId(report)))
|
|
330
|
-
return {
|
|
331
|
-
ok: false,
|
|
332
|
-
code: 'evidence_id_invalid',
|
|
333
|
-
message: 'Evidence ids must be null or safe registered message ids.',
|
|
334
|
-
};
|
|
335
|
-
if (value['archive_mode'] !== 'task-retirement')
|
|
336
|
-
return {
|
|
337
|
-
ok: false,
|
|
338
|
-
code: 'archive_mode_invalid',
|
|
339
|
-
message: 'Only the reversible task-retirement marker is supported; native archive is unavailable.',
|
|
340
|
-
};
|
|
341
|
-
const scopeValue = value['managed_execution_scope'];
|
|
342
|
-
const managedExecutionScope = scopeValue === undefined ? null : parseManagedPiExecutionScope(scopeValue);
|
|
343
|
-
if (scopeValue !== undefined &&
|
|
344
|
-
(managedExecutionScope === null ||
|
|
345
|
-
managedExecutionScope.task_id !== path.basename(value['task_root']) ||
|
|
346
|
-
!safePathEqual(managedExecutionScope.task_root, value['task_root']) ||
|
|
347
|
-
managedExecutionScope.session_id !== intent.from.session_id ||
|
|
348
|
-
!safePathEqual(managedExecutionScope.cwd, intent.from.cwd ?? '') ||
|
|
349
|
-
managedExecutionScope.subtask_id !== intent.subtask_id ||
|
|
350
|
-
managedExecutionScope.attempt !== intent.attempt ||
|
|
351
|
-
managedExecutionScope.owner_epoch !== intent.coordinator_epoch ||
|
|
352
|
-
managedExecutionScope.claim_epochs.length !== intent.claims.length ||
|
|
353
|
-
managedExecutionScope.claim_epochs.some((pin, index) => pin.claim_id !== intent.claims[index]?.claim_id ||
|
|
354
|
-
pin.resource_epoch !== intent.claims[index]?.resource_epoch)))
|
|
355
|
-
return {
|
|
356
|
-
ok: false,
|
|
357
|
-
code: 'managed_execution_scope_mismatch',
|
|
358
|
-
message: 'Managed execution scope must exactly match task, source session, attempt, owner epoch and claim pins.',
|
|
359
|
-
};
|
|
360
|
-
return {
|
|
361
|
-
ok: true,
|
|
362
|
-
request: {
|
|
363
|
-
schema: COLLAB_SESSION_TRANSITION_REQUEST_SCHEMA,
|
|
364
|
-
task_root: value['task_root'],
|
|
365
|
-
transition_id: value['transition_id'],
|
|
366
|
-
intent,
|
|
367
|
-
bundle: {
|
|
368
|
-
root: bundleValue['root'],
|
|
369
|
-
continuity_task_id: bundleValue['continuity_task_id'],
|
|
370
|
-
bundle_id: bundleValue['bundle_id'],
|
|
371
|
-
digest: bundleValue['digest'],
|
|
372
|
-
},
|
|
373
|
-
bootstrap_request: {
|
|
374
|
-
ref: bootstrapValue['ref'],
|
|
375
|
-
digest: bootstrapValue['digest'],
|
|
376
|
-
},
|
|
377
|
-
evidence: {
|
|
378
|
-
ack_message_id: typeof ack === 'string' ? ack : null,
|
|
379
|
-
first_work_report_id: typeof report === 'string' ? report : null,
|
|
380
|
-
},
|
|
381
|
-
archive_mode: 'task-retirement',
|
|
382
|
-
...(managedExecutionScope === null
|
|
383
|
-
? {}
|
|
384
|
-
: { managed_execution_scope: managedExecutionScope }),
|
|
385
|
-
},
|
|
386
|
-
};
|
|
387
|
-
}
|
|
388
|
-
export function loadSessionTransitionRequest(file) {
|
|
389
|
-
const read = readBoundedFile(file, MAX_REQUEST_BYTES);
|
|
390
|
-
if (!read.ok)
|
|
391
|
-
return {
|
|
392
|
-
ok: false,
|
|
393
|
-
kind: 'invalid',
|
|
394
|
-
code: 'request_unreadable',
|
|
395
|
-
message: `Request must be an absolute bounded regular file (${read.code}).`,
|
|
396
|
-
next_action: 'Pass a regular JSON request file; no task mutation was attempted.',
|
|
397
|
-
};
|
|
398
|
-
let parsed;
|
|
399
|
-
try {
|
|
400
|
-
parsed = JSON.parse(read.text);
|
|
401
|
-
}
|
|
402
|
-
catch {
|
|
403
|
-
return {
|
|
404
|
-
ok: false,
|
|
405
|
-
kind: 'invalid',
|
|
406
|
-
code: 'request_invalid_json',
|
|
407
|
-
message: 'Session-transition request is not valid JSON.',
|
|
408
|
-
next_action: 'Fix the request document and retry explicitly.',
|
|
409
|
-
};
|
|
410
|
-
}
|
|
411
|
-
const validated = validateSessionTransitionRequest(parsed);
|
|
412
|
-
if (!validated.ok)
|
|
413
|
-
return { ...validated, kind: 'invalid', next_action: null };
|
|
414
|
-
return {
|
|
415
|
-
ok: true,
|
|
416
|
-
value: {
|
|
417
|
-
request: validated.request,
|
|
418
|
-
file,
|
|
419
|
-
file_digest: read.digest,
|
|
420
|
-
immutable_digest: immutableDigest(validated.request),
|
|
421
|
-
},
|
|
422
|
-
};
|
|
423
|
-
}
|
|
424
60
|
function requestOf(value) {
|
|
425
61
|
return 'request' in value ? value.request : value;
|
|
426
62
|
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { CliOutput } from './output.js';
|
|
2
|
+
import { type CliValues } from './options.js';
|
|
3
|
+
import type { getPackageInfo } from '../package-info.js';
|
|
4
|
+
/** Everything the collaboration router needs from one parsed command line. */
|
|
5
|
+
export interface CollaborationRouteRequest {
|
|
6
|
+
readonly values: CliValues;
|
|
7
|
+
/** The resolved `collab` action, e.g. `message:send` or `role-run`. */
|
|
8
|
+
readonly action: string | null;
|
|
9
|
+
/** Raw argv, for duplicate-flag detection the parser cannot observe. */
|
|
10
|
+
readonly args: readonly string[];
|
|
11
|
+
readonly info: ReturnType<typeof getPackageInfo>;
|
|
12
|
+
readonly rpcRequestTimeout: string | undefined;
|
|
13
|
+
readonly rpcOperationTimeout: string | undefined;
|
|
14
|
+
readonly output: CliOutput;
|
|
15
|
+
readonly json: boolean;
|
|
16
|
+
}
|
|
17
|
+
export declare function routeCollaboration(request: CollaborationRouteRequest): Promise<number>;
|