@united-workforce/cli 0.7.0 → 0.8.1
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 +32 -5
- package/dist/.build-fingerprint +1 -0
- package/dist/__tests__/broker-step-active-turns.test.d.ts +20 -0
- package/dist/__tests__/broker-step-active-turns.test.d.ts.map +1 -0
- package/dist/__tests__/broker-step-active-turns.test.js +428 -0
- package/dist/__tests__/broker-step-active-turns.test.js.map +1 -0
- package/dist/__tests__/broker-step-turn-chain-phase2.test.d.ts +13 -0
- package/dist/__tests__/broker-step-turn-chain-phase2.test.d.ts.map +1 -0
- package/dist/__tests__/broker-step-turn-chain-phase2.test.js +429 -0
- package/dist/__tests__/broker-step-turn-chain-phase2.test.js.map +1 -0
- package/dist/__tests__/e2e-broker-step-suspend.test.d.ts +18 -0
- package/dist/__tests__/e2e-broker-step-suspend.test.d.ts.map +1 -0
- package/dist/__tests__/e2e-broker-step-suspend.test.js +313 -0
- package/dist/__tests__/e2e-broker-step-suspend.test.js.map +1 -0
- package/dist/__tests__/e2e-thread-resume-timeout-suspend.test.d.ts +28 -0
- package/dist/__tests__/e2e-thread-resume-timeout-suspend.test.d.ts.map +1 -0
- package/dist/__tests__/e2e-thread-resume-timeout-suspend.test.js +322 -0
- package/dist/__tests__/e2e-thread-resume-timeout-suspend.test.js.map +1 -0
- package/dist/__tests__/log-tag-validity.test.d.ts +2 -0
- package/dist/__tests__/log-tag-validity.test.d.ts.map +1 -0
- package/dist/__tests__/log-tag-validity.test.js +110 -0
- package/dist/__tests__/log-tag-validity.test.js.map +1 -0
- package/dist/__tests__/setup-agent-discovery.test.js +23 -23
- package/dist/__tests__/setup-agent-discovery.test.js.map +1 -1
- package/dist/__tests__/step-show-json.test.js +5 -5
- package/dist/__tests__/step-show-json.test.js.map +1 -1
- package/dist/__tests__/step-show-text.test.d.ts +2 -0
- package/dist/__tests__/step-show-text.test.d.ts.map +1 -0
- package/dist/__tests__/step-show-text.test.js +192 -0
- package/dist/__tests__/step-show-text.test.js.map +1 -0
- package/dist/__tests__/step-turns-cli-subprocess.test.d.ts +21 -0
- package/dist/__tests__/step-turns-cli-subprocess.test.d.ts.map +1 -0
- package/dist/__tests__/step-turns-cli-subprocess.test.js +356 -0
- package/dist/__tests__/step-turns-cli-subprocess.test.js.map +1 -0
- package/dist/__tests__/step-turns-panorama-phase3.test.d.ts +21 -0
- package/dist/__tests__/step-turns-panorama-phase3.test.d.ts.map +1 -0
- package/dist/__tests__/step-turns-panorama-phase3.test.js +476 -0
- package/dist/__tests__/step-turns-panorama-phase3.test.js.map +1 -0
- package/dist/__tests__/step-turns.test.d.ts +24 -0
- package/dist/__tests__/step-turns.test.d.ts.map +1 -0
- package/dist/__tests__/step-turns.test.js +646 -0
- package/dist/__tests__/step-turns.test.js.map +1 -0
- package/dist/__tests__/store-turn-chain.test.d.ts +2 -0
- package/dist/__tests__/store-turn-chain.test.d.ts.map +1 -0
- package/dist/__tests__/store-turn-chain.test.js +341 -0
- package/dist/__tests__/store-turn-chain.test.js.map +1 -0
- package/dist/__tests__/thread-list-limit-offset.test.d.ts +24 -0
- package/dist/__tests__/thread-list-limit-offset.test.d.ts.map +1 -0
- package/dist/__tests__/thread-list-limit-offset.test.js +254 -0
- package/dist/__tests__/thread-list-limit-offset.test.js.map +1 -0
- package/dist/__tests__/thread-list-template-ms-date.test.js +7 -2
- package/dist/__tests__/thread-list-template-ms-date.test.js.map +1 -1
- package/dist/__tests__/thread.test.js +28 -14
- package/dist/__tests__/thread.test.js.map +1 -1
- package/dist/cli.js +910 -344
- package/dist/cli.js.map +1 -1
- package/dist/commands/broker-step.d.ts +10 -3
- package/dist/commands/broker-step.d.ts.map +1 -1
- package/dist/commands/broker-step.js +231 -27
- package/dist/commands/broker-step.js.map +1 -1
- package/dist/commands/prompt.d.ts.map +1 -1
- package/dist/commands/prompt.js +42 -50
- package/dist/commands/prompt.js.map +1 -1
- package/dist/commands/setup.d.ts +6 -4
- package/dist/commands/setup.d.ts.map +1 -1
- package/dist/commands/setup.js +16 -26
- package/dist/commands/setup.js.map +1 -1
- package/dist/commands/step.d.ts +48 -1
- package/dist/commands/step.d.ts.map +1 -1
- package/dist/commands/step.js +496 -3
- package/dist/commands/step.js.map +1 -1
- package/dist/output-mappers.d.ts +8 -0
- package/dist/output-mappers.d.ts.map +1 -1
- package/dist/output-mappers.js +72 -18
- package/dist/output-mappers.js.map +1 -1
- package/dist/schemas.d.ts +3 -0
- package/dist/schemas.d.ts.map +1 -1
- package/dist/schemas.js +17 -3
- package/dist/schemas.js.map +1 -1
- package/dist/store.d.ts +147 -1
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +254 -1
- package/dist/store.js.map +1 -1
- package/dist/text-renderers.d.ts.map +1 -1
- package/dist/text-renderers.js +27 -2
- package/dist/text-renderers.js.map +1 -1
- package/package.json +7 -6
- package/src/__tests__/broker-step-active-turns.test.ts +509 -0
- package/src/__tests__/broker-step-turn-chain-phase2.test.ts +525 -0
- package/src/__tests__/e2e-broker-step-suspend.test.ts +351 -0
- package/src/__tests__/e2e-thread-resume-timeout-suspend.test.ts +360 -0
- package/src/__tests__/log-tag-validity.test.ts +124 -0
- package/src/__tests__/setup-agent-discovery.test.ts +23 -23
- package/src/__tests__/step-show-json.test.ts +5 -5
- package/src/__tests__/step-show-text.test.ts +236 -0
- package/src/__tests__/step-turns-cli-subprocess.test.ts +411 -0
- package/src/__tests__/step-turns-panorama-phase3.test.ts +579 -0
- package/src/__tests__/step-turns.test.ts +734 -0
- package/src/__tests__/store-turn-chain.test.ts +386 -0
- package/src/__tests__/thread-list-limit-offset.test.ts +305 -0
- package/src/__tests__/thread-list-template-ms-date.test.ts +7 -2
- package/src/__tests__/thread.test.ts +29 -15
- package/src/cli.ts +1056 -483
- package/src/commands/broker-step.ts +315 -38
- package/src/commands/prompt.ts +42 -50
- package/src/commands/setup.ts +16 -28
- package/src/commands/step.ts +655 -3
- package/src/output-mappers.ts +99 -21
- package/src/schemas.ts +32 -2
- package/src/store.ts +297 -2
- package/src/text-renderers.ts +35 -2
|
@@ -11,22 +11,24 @@ import { join } from "node:path";
|
|
|
11
11
|
import { putSchema, validate } from "@ocas/core";
|
|
12
12
|
import {
|
|
13
13
|
type AgentRoute,
|
|
14
|
+
type BrokerTurn,
|
|
14
15
|
createBroker,
|
|
15
16
|
createSessionStore,
|
|
16
17
|
type SendResult,
|
|
17
18
|
type SessionStore,
|
|
18
19
|
} from "@united-workforce/broker";
|
|
19
|
-
import
|
|
20
|
-
AgentAlias,
|
|
21
|
-
AgentConfig,
|
|
22
|
-
CasRef,
|
|
23
|
-
StartNodePayload,
|
|
24
|
-
StepContext,
|
|
25
|
-
StepNodePayload,
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
20
|
+
import {
|
|
21
|
+
type AgentAlias,
|
|
22
|
+
type AgentConfig,
|
|
23
|
+
type CasRef,
|
|
24
|
+
type StartNodePayload,
|
|
25
|
+
type StepContext,
|
|
26
|
+
type StepNodePayload,
|
|
27
|
+
SUSPEND_STATUS,
|
|
28
|
+
type ThreadId,
|
|
29
|
+
type Usage,
|
|
30
|
+
type WorkflowConfig,
|
|
31
|
+
type WorkflowPayload,
|
|
30
32
|
} from "@united-workforce/protocol";
|
|
31
33
|
import { createLogger, type ProcessLogger } from "@united-workforce/util";
|
|
32
34
|
import {
|
|
@@ -39,7 +41,16 @@ import {
|
|
|
39
41
|
tryFrontmatterFastPath,
|
|
40
42
|
trySuspendFastPath,
|
|
41
43
|
} from "@united-workforce/util-agent";
|
|
42
|
-
import
|
|
44
|
+
import {
|
|
45
|
+
clearActiveStep,
|
|
46
|
+
clearActiveTurns,
|
|
47
|
+
getActiveTurnHead,
|
|
48
|
+
setActiveStep,
|
|
49
|
+
setActiveTurnHead,
|
|
50
|
+
type UwfStore,
|
|
51
|
+
writeStepStart,
|
|
52
|
+
writeTurnNode,
|
|
53
|
+
} from "../store.js";
|
|
43
54
|
import { expandOutput, fail } from "./shared.js";
|
|
44
55
|
|
|
45
56
|
const log = createLogger({ sink: { kind: "stderr" } });
|
|
@@ -49,33 +60,24 @@ const PL_BROKER_SEND = "BR0KR5ND";
|
|
|
49
60
|
/** Tag for frontmatter retry call sites. */
|
|
50
61
|
const PL_FRONTMATTER_RETRY = "F4RTM4RT";
|
|
51
62
|
/** Tag for frontmatter extraction failure. */
|
|
52
|
-
const PL_FRONTMATTER_FAIL = "
|
|
63
|
+
const PL_FRONTMATTER_FAIL = "F4FA117Z";
|
|
53
64
|
|
|
54
65
|
const MAX_FRONTMATTER_RETRIES = 2;
|
|
55
66
|
|
|
56
|
-
const TURN_SCHEMA = {
|
|
57
|
-
title: "broker-turn",
|
|
58
|
-
type: "object" as const,
|
|
59
|
-
required: ["role", "content"],
|
|
60
|
-
properties: {
|
|
61
|
-
role: { type: "string" as const, enum: ["assistant", "tool"] },
|
|
62
|
-
content: { type: "string" as const },
|
|
63
|
-
},
|
|
64
|
-
additionalProperties: false,
|
|
65
|
-
};
|
|
66
|
-
|
|
67
67
|
const DETAIL_SCHEMA = {
|
|
68
68
|
title: "broker-detail",
|
|
69
69
|
type: "object" as const,
|
|
70
|
-
required: ["sessionId", "duration", "turnCount"
|
|
70
|
+
required: ["sessionId", "duration", "turnCount"],
|
|
71
71
|
properties: {
|
|
72
72
|
sessionId: { type: "string" as const },
|
|
73
73
|
duration: { type: "integer" as const },
|
|
74
74
|
turnCount: { type: "integer" as const },
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
},
|
|
75
|
+
// Suspend diagnostics (issue #435) — present only on a timeout-suspended
|
|
76
|
+
// step. Optional, so the completed-path detail node is byte-for-byte
|
|
77
|
+
// unchanged (same content hash).
|
|
78
|
+
nativeId: { type: "string" as const },
|
|
79
|
+
elapsedMs: { type: "integer" as const },
|
|
80
|
+
reason: { type: "string" as const },
|
|
79
81
|
},
|
|
80
82
|
additionalProperties: false,
|
|
81
83
|
};
|
|
@@ -328,28 +330,83 @@ export function assembleBrokerPrompt(args: AssembleBrokerPromptArgs): string {
|
|
|
328
330
|
return parts.join("\n");
|
|
329
331
|
}
|
|
330
332
|
|
|
331
|
-
/**
|
|
333
|
+
/**
|
|
334
|
+
* Persist the step's detail node. Phase 2 (#419): the detail no longer contains
|
|
335
|
+
* a `turns` array — turns are self-contained via their `prev`+`owner` chain.
|
|
336
|
+
* Only metadata (sessionId, duration, turnCount) is stored.
|
|
337
|
+
*/
|
|
332
338
|
async function storeBrokerDetail(
|
|
333
339
|
uwf: UwfStore,
|
|
334
340
|
result: SendResult,
|
|
341
|
+
threadId: ThreadId,
|
|
342
|
+
role: string,
|
|
335
343
|
startedAtMs: number,
|
|
336
344
|
completedAtMs: number,
|
|
345
|
+
turnCount: number,
|
|
337
346
|
): Promise<CasRef> {
|
|
338
|
-
const turnSchemaHash = await putSchema(uwf.store, TURN_SCHEMA);
|
|
339
347
|
const detailSchemaHash = await putSchema(uwf.store, DETAIL_SCHEMA);
|
|
340
348
|
|
|
341
|
-
|
|
342
|
-
|
|
349
|
+
// Phase 2 (#419): clear the deprecated role-keyed active var for backward
|
|
350
|
+
// compatibility. The turns are already persisted via the turn chain.
|
|
351
|
+
clearActiveTurns(uwf.store, threadId, role);
|
|
343
352
|
|
|
344
353
|
const detail = {
|
|
345
354
|
sessionId: result.sessionId,
|
|
346
355
|
duration: Math.max(0, completedAtMs - startedAtMs),
|
|
347
|
-
turnCount
|
|
348
|
-
turns: [turnHash],
|
|
356
|
+
turnCount,
|
|
349
357
|
};
|
|
350
358
|
return uwf.store.cas.put(detailSchemaHash, detail);
|
|
351
359
|
}
|
|
352
360
|
|
|
361
|
+
/**
|
|
362
|
+
* Build the realtime `onTurn` callback wired into `broker.send` (Phase 2, #419).
|
|
363
|
+
* For each arriving assistant turn it writes a TurnNode with:
|
|
364
|
+
* - `role: "assistant"`
|
|
365
|
+
* - `content: <turn content>`
|
|
366
|
+
* - `prev: <previous turn hash or null>`
|
|
367
|
+
* - `owner: <current step-start hash>`
|
|
368
|
+
* Then updates `@uwf/active-turn-head/<threadId>` to point to the new turn.
|
|
369
|
+
*
|
|
370
|
+
* The turn chain is self-contained — each turn links to its predecessor via
|
|
371
|
+
* `prev` and to its owning step via `owner`. No separate array accumulation
|
|
372
|
+
* is needed.
|
|
373
|
+
*
|
|
374
|
+
* Returns the turn count after the step completes (for detail node).
|
|
375
|
+
*/
|
|
376
|
+
function makeOnTurn(
|
|
377
|
+
uwf: UwfStore,
|
|
378
|
+
threadId: ThreadId,
|
|
379
|
+
stepStartHash: CasRef,
|
|
380
|
+
): { onTurn: (turn: BrokerTurn) => void; getTurnCount: () => number } {
|
|
381
|
+
let turnCount = 0;
|
|
382
|
+
// Get the current turn head before this step starts (could be from previous steps)
|
|
383
|
+
let prevTurnHash: CasRef | null = getActiveTurnHead(uwf.store, threadId);
|
|
384
|
+
|
|
385
|
+
const onTurn = (turn: BrokerTurn): void => {
|
|
386
|
+
// Write turn node with prev+owner chain
|
|
387
|
+
const turnHash = writeTurnNode(uwf, {
|
|
388
|
+
role: "assistant",
|
|
389
|
+
content: turn.content,
|
|
390
|
+
prev: prevTurnHash,
|
|
391
|
+
owner: stepStartHash,
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
// Update thread-keyed active turn head
|
|
395
|
+
setActiveTurnHead(uwf.store, threadId, turnHash);
|
|
396
|
+
|
|
397
|
+
// Also maintain deprecated role-keyed var for backward compatibility
|
|
398
|
+
// during transition period (can be removed in Phase 3)
|
|
399
|
+
// appendActiveTurn is called but we don't rely on it for turn retrieval
|
|
400
|
+
|
|
401
|
+
prevTurnHash = turnHash;
|
|
402
|
+
turnCount++;
|
|
403
|
+
};
|
|
404
|
+
|
|
405
|
+
const getTurnCount = (): number => turnCount;
|
|
406
|
+
|
|
407
|
+
return { onTurn, getTurnCount };
|
|
408
|
+
}
|
|
409
|
+
|
|
353
410
|
type WriteStepNodeArgs = {
|
|
354
411
|
uwf: UwfStore;
|
|
355
412
|
startHash: CasRef;
|
|
@@ -398,6 +455,124 @@ type ExtractOutcome = {
|
|
|
398
455
|
body: string;
|
|
399
456
|
};
|
|
400
457
|
|
|
458
|
+
/**
|
|
459
|
+
* Render the engine-level suspend (coroutine yield) wire format — frontmatter
|
|
460
|
+
* with `$status: "$SUSPEND"` plus a human-readable `reason`. Round-trips through
|
|
461
|
+
* the public {@link trySuspendFastPath}, which stores it against the reserved
|
|
462
|
+
* suspend-output schema.
|
|
463
|
+
*
|
|
464
|
+
* NOTE: this mirrors the adapter-side `buildSuspendOutput` in
|
|
465
|
+
* `@united-workforce/util-agent`, kept private here on purpose — the #381
|
|
466
|
+
* public-API cleanup deliberately keeps that helper OUT of the util-agent
|
|
467
|
+
* barrel, and `broker-step.ts` is engine/CLI code (not an adapter). The string
|
|
468
|
+
* is a one-liner over `SUSPEND_STATUS`, so duplicating it costs nothing and
|
|
469
|
+
* preserves the package boundary (see public-api-no-llm.test.ts).
|
|
470
|
+
*/
|
|
471
|
+
function buildSuspendOutput(reason: string): string {
|
|
472
|
+
return `---\n$status: ${SUSPEND_STATUS}\nreason: ${reason}\n---\n`;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
/**
|
|
476
|
+
* Suspend metadata carried by a broker `kind:"suspended"` SendResult — the
|
|
477
|
+
* fields needed to (a) build the human-readable `$SUSPEND` reason and (b)
|
|
478
|
+
* record diagnostics on the detail node for a future `--resume`.
|
|
479
|
+
*/
|
|
480
|
+
type SuspendInfo = Readonly<{
|
|
481
|
+
reason: "timeout";
|
|
482
|
+
nativeId: string;
|
|
483
|
+
elapsedMs: number;
|
|
484
|
+
}>;
|
|
485
|
+
|
|
486
|
+
type WriteSuspendedStepArgs = {
|
|
487
|
+
uwf: UwfStore;
|
|
488
|
+
threadId: ThreadId;
|
|
489
|
+
suspend: SuspendInfo;
|
|
490
|
+
sessionId: string;
|
|
491
|
+
turnCount: number;
|
|
492
|
+
startHash: CasRef;
|
|
493
|
+
prevHash: CasRef | null;
|
|
494
|
+
role: string;
|
|
495
|
+
agentName: string;
|
|
496
|
+
edgePrompt: string;
|
|
497
|
+
startedAtMs: number;
|
|
498
|
+
completedAtMs: number;
|
|
499
|
+
cwd: string;
|
|
500
|
+
assembledPromptHash: CasRef | null;
|
|
501
|
+
previousAttempts: CasRef[] | null;
|
|
502
|
+
};
|
|
503
|
+
|
|
504
|
+
/**
|
|
505
|
+
* Route a broker `kind:"suspended"` result through the existing engine-level
|
|
506
|
+
* `$SUSPEND` exit (issue #435, Phase 2). A send timeout is NOT an error and NOT
|
|
507
|
+
* a frontmatter failure — it is a human gate. We build a suspend output via the
|
|
508
|
+
* shared {@link buildSuspendOutput} / {@link trySuspendFastPath} helpers (the
|
|
509
|
+
* same wire format any agent that prints `$status: "$SUSPEND"` produces), store
|
|
510
|
+
* it against the reserved `suspendOutput` schema, record `nativeId`/`elapsedMs`
|
|
511
|
+
* on the detail node for diagnostics, and persist a normal StepNode. Downstream
|
|
512
|
+
* thread-status resolution maps the head step's `$status: "$SUSPEND"` output to
|
|
513
|
+
* `status: "suspended"`, and `uwf thread resume` continues from `nativeId`.
|
|
514
|
+
*/
|
|
515
|
+
async function writeSuspendedStep(args: WriteSuspendedStepArgs): Promise<BrokerStepResult> {
|
|
516
|
+
const reason =
|
|
517
|
+
`sumeru send timed out after ${args.suspend.elapsedMs}ms ` +
|
|
518
|
+
`(nativeId=${args.suspend.nativeId}); resume to continue`;
|
|
519
|
+
const suspendRaw = buildSuspendOutput(reason);
|
|
520
|
+
const extracted = await trySuspendFastPath(
|
|
521
|
+
suspendRaw,
|
|
522
|
+
args.uwf.schemas.suspendOutput,
|
|
523
|
+
args.uwf.store,
|
|
524
|
+
);
|
|
525
|
+
if (extracted === null) {
|
|
526
|
+
fail("broker step failed to build a $SUSPEND output node for a timeout-suspended send");
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
const detailSchemaHash = await putSchema(args.uwf.store, DETAIL_SCHEMA);
|
|
530
|
+
// Clear the deprecated role-keyed active var (parity with storeBrokerDetail).
|
|
531
|
+
clearActiveTurns(args.uwf.store, args.threadId, args.role);
|
|
532
|
+
const detail = {
|
|
533
|
+
sessionId: args.sessionId,
|
|
534
|
+
duration: Math.max(0, args.completedAtMs - args.startedAtMs),
|
|
535
|
+
turnCount: args.turnCount,
|
|
536
|
+
nativeId: args.suspend.nativeId,
|
|
537
|
+
elapsedMs: args.suspend.elapsedMs,
|
|
538
|
+
reason: args.suspend.reason,
|
|
539
|
+
};
|
|
540
|
+
const detailHash = await args.uwf.store.cas.put(detailSchemaHash, detail);
|
|
541
|
+
|
|
542
|
+
// Clear the active-step var: the step has reached a terminal (suspended) state.
|
|
543
|
+
clearActiveStep(args.uwf.store, args.threadId);
|
|
544
|
+
|
|
545
|
+
const stepHash = await writeBrokerStepNode({
|
|
546
|
+
uwf: args.uwf,
|
|
547
|
+
startHash: args.startHash,
|
|
548
|
+
prevHash: args.prevHash,
|
|
549
|
+
role: args.role,
|
|
550
|
+
outputHash: extracted.outputHash,
|
|
551
|
+
detailHash,
|
|
552
|
+
agentName: args.agentName,
|
|
553
|
+
edgePrompt: args.edgePrompt,
|
|
554
|
+
startedAtMs: args.startedAtMs,
|
|
555
|
+
completedAtMs: args.completedAtMs,
|
|
556
|
+
cwd: args.cwd,
|
|
557
|
+
assembledPromptHash: args.assembledPromptHash,
|
|
558
|
+
usage: null,
|
|
559
|
+
previousAttempts: args.previousAttempts,
|
|
560
|
+
});
|
|
561
|
+
|
|
562
|
+
return {
|
|
563
|
+
stepHash,
|
|
564
|
+
detailHash,
|
|
565
|
+
role: args.role,
|
|
566
|
+
frontmatter: extracted.frontmatter,
|
|
567
|
+
body: extracted.body,
|
|
568
|
+
startedAtMs: args.startedAtMs,
|
|
569
|
+
completedAtMs: args.completedAtMs,
|
|
570
|
+
usage: null,
|
|
571
|
+
isError: false,
|
|
572
|
+
errorMessage: null,
|
|
573
|
+
};
|
|
574
|
+
}
|
|
575
|
+
|
|
401
576
|
async function tryExtract(
|
|
402
577
|
uwf: UwfStore,
|
|
403
578
|
rawOutput: string,
|
|
@@ -447,9 +622,16 @@ export type ExecuteBrokerStepArgs = {
|
|
|
447
622
|
* persistence. Returns a `BrokerStepResult` shaped for the existing
|
|
448
623
|
* `executeAndProcessAgentStep` flow.
|
|
449
624
|
*
|
|
625
|
+
* Phase 2 (#419) changes:
|
|
626
|
+
* - Writes step-start node at entry, sets `@uwf/active-step/<threadId>`
|
|
627
|
+
* - Turns are written with `prev`+`owner` chain via `writeTurnNode`
|
|
628
|
+
* - Updates `@uwf/active-turn-head/<threadId>` as turns arrive
|
|
629
|
+
* - Clears `@uwf/active-step/<threadId>` at completion
|
|
630
|
+
* - Detail node no longer contains `turns` array (turns self-contained)
|
|
631
|
+
*
|
|
450
632
|
* Side effects:
|
|
451
633
|
* - inserts a row in the broker session store keyed by (threadId, role)
|
|
452
|
-
* - writes
|
|
634
|
+
* - writes step-start / turns / detail / StepNode to CAS
|
|
453
635
|
* - on extraction failure, persists an error StepNode (isError=true)
|
|
454
636
|
*/
|
|
455
637
|
export async function executeBrokerStep(args: ExecuteBrokerStepArgs): Promise<BrokerStepResult> {
|
|
@@ -500,12 +682,67 @@ export async function executeBrokerStep(args: ExecuteBrokerStepArgs): Promise<Br
|
|
|
500
682
|
)) as CasRef;
|
|
501
683
|
|
|
502
684
|
const startedAtMs = Date.now();
|
|
685
|
+
|
|
686
|
+
// Phase 2 (#419): Write step-start node at entry
|
|
687
|
+
const stepStartHash = writeStepStart(args.uwf, {
|
|
688
|
+
role: args.role,
|
|
689
|
+
edgePrompt: args.edgePrompt,
|
|
690
|
+
stepIndex: steps.length,
|
|
691
|
+
prev: args.prevHash,
|
|
692
|
+
start: args.startHash,
|
|
693
|
+
startedAtMs,
|
|
694
|
+
cwd: args.effectiveCwd,
|
|
695
|
+
});
|
|
696
|
+
|
|
697
|
+
// Set the active-step var so other processes can detect in-flight state
|
|
698
|
+
setActiveStep(args.uwf.store, args.threadId, stepStartHash);
|
|
699
|
+
|
|
700
|
+
// Start-of-step clear (Phase 2, #398): a crash-rerun is a fresh attempt, so
|
|
701
|
+
// any residual active var from a failed prior attempt is dropped here —
|
|
702
|
+
// before any onTurn can fire — rather than appended onto. The clear is
|
|
703
|
+
// start-of-step only (NOT per-send): frontmatter retries below re-send on
|
|
704
|
+
// the cached session and must keep appending to the same attempt's var.
|
|
705
|
+
clearActiveTurns(args.uwf.store, args.threadId, args.role);
|
|
706
|
+
|
|
707
|
+
// Phase 2 (#419): makeOnTurn now writes turns with prev+owner chain
|
|
708
|
+
const { onTurn, getTurnCount } = makeOnTurn(args.uwf, args.threadId, stepStartHash);
|
|
709
|
+
|
|
503
710
|
const primary = await broker.send({
|
|
504
711
|
threadId: args.threadId,
|
|
505
712
|
role: args.role,
|
|
506
713
|
prompt: assembledPrompt,
|
|
714
|
+
onTurn,
|
|
507
715
|
});
|
|
508
716
|
|
|
717
|
+
// Suspend gate (issue #435, Phase 2): a broker `kind:"suspended"` result
|
|
718
|
+
// means the Sumeru send hit a timeout and emitted RFC #95 `suspend`. Route
|
|
719
|
+
// it through the existing `$SUSPEND` exit BEFORE any frontmatter work —
|
|
720
|
+
// suspend is a human gate, never retried, never an error. TypeScript's
|
|
721
|
+
// discriminated union forces this narrow before any `primary.output` read.
|
|
722
|
+
if (primary.kind === "suspended") {
|
|
723
|
+
return writeSuspendedStep({
|
|
724
|
+
uwf: args.uwf,
|
|
725
|
+
threadId: args.threadId,
|
|
726
|
+
suspend: {
|
|
727
|
+
reason: primary.reason,
|
|
728
|
+
nativeId: primary.nativeId,
|
|
729
|
+
elapsedMs: primary.elapsedMs,
|
|
730
|
+
},
|
|
731
|
+
sessionId: primary.sessionId,
|
|
732
|
+
turnCount: getTurnCount(),
|
|
733
|
+
startHash: args.startHash,
|
|
734
|
+
prevHash: args.prevHash,
|
|
735
|
+
role: args.role,
|
|
736
|
+
agentName: route.gateway,
|
|
737
|
+
edgePrompt: args.edgePrompt,
|
|
738
|
+
startedAtMs,
|
|
739
|
+
completedAtMs: Date.now(),
|
|
740
|
+
cwd: args.effectiveCwd,
|
|
741
|
+
assembledPromptHash,
|
|
742
|
+
previousAttempts: args.previousAttempts,
|
|
743
|
+
});
|
|
744
|
+
}
|
|
745
|
+
|
|
509
746
|
let extracted = await tryExtract(args.uwf, primary.output, outputSchemaHash);
|
|
510
747
|
let accumulatedUsage: Usage | null = brokerUsage(primary);
|
|
511
748
|
let lastOutput = primary.output;
|
|
@@ -513,7 +750,8 @@ export async function executeBrokerStep(args: ExecuteBrokerStepArgs): Promise<Br
|
|
|
513
750
|
|
|
514
751
|
// Retry on the same (threadId, role) — the broker re-uses the cached
|
|
515
752
|
// Sumeru session, so the agent gets to "fix its frontmatter" with full
|
|
516
|
-
// context preserved.
|
|
753
|
+
// context preserved. Retries carry the same onTurn and keep appending to
|
|
754
|
+
// the same attempt's active var (no clear between retries).
|
|
517
755
|
for (let retry = 0; retry < MAX_FRONTMATTER_RETRIES && extracted === null; retry++) {
|
|
518
756
|
const correctionPrompt = buildFrontmatterRetryPrompt(outputFormatInstruction);
|
|
519
757
|
log(
|
|
@@ -524,7 +762,33 @@ export async function executeBrokerStep(args: ExecuteBrokerStepArgs): Promise<Br
|
|
|
524
762
|
threadId: args.threadId,
|
|
525
763
|
role: args.role,
|
|
526
764
|
prompt: correctionPrompt,
|
|
765
|
+
onTurn,
|
|
527
766
|
});
|
|
767
|
+
// A retry can itself time out — honor the same suspend gate rather than
|
|
768
|
+
// dereferencing `retryResult.output` on a suspended result.
|
|
769
|
+
if (retryResult.kind === "suspended") {
|
|
770
|
+
return writeSuspendedStep({
|
|
771
|
+
uwf: args.uwf,
|
|
772
|
+
threadId: args.threadId,
|
|
773
|
+
suspend: {
|
|
774
|
+
reason: retryResult.reason,
|
|
775
|
+
nativeId: retryResult.nativeId,
|
|
776
|
+
elapsedMs: retryResult.elapsedMs,
|
|
777
|
+
},
|
|
778
|
+
sessionId: retryResult.sessionId,
|
|
779
|
+
turnCount: getTurnCount(),
|
|
780
|
+
startHash: args.startHash,
|
|
781
|
+
prevHash: args.prevHash,
|
|
782
|
+
role: args.role,
|
|
783
|
+
agentName: route.gateway,
|
|
784
|
+
edgePrompt: args.edgePrompt,
|
|
785
|
+
startedAtMs,
|
|
786
|
+
completedAtMs: Date.now(),
|
|
787
|
+
cwd: args.effectiveCwd,
|
|
788
|
+
assembledPromptHash,
|
|
789
|
+
previousAttempts: args.previousAttempts,
|
|
790
|
+
});
|
|
791
|
+
}
|
|
528
792
|
lastOutput = retryResult.output;
|
|
529
793
|
lastSessionId = retryResult.sessionId;
|
|
530
794
|
accumulatedUsage = mergeUsage(accumulatedUsage, brokerUsage(retryResult));
|
|
@@ -532,13 +796,21 @@ export async function executeBrokerStep(args: ExecuteBrokerStepArgs): Promise<Br
|
|
|
532
796
|
}
|
|
533
797
|
|
|
534
798
|
const completedAtMs = Date.now();
|
|
799
|
+
|
|
800
|
+
// Phase 2 (#419): Pass turn count to detail (no longer from active var)
|
|
535
801
|
const detailHash = await storeBrokerDetail(
|
|
536
802
|
args.uwf,
|
|
537
803
|
{ ...primary, output: lastOutput, sessionId: lastSessionId },
|
|
804
|
+
args.threadId,
|
|
805
|
+
args.role,
|
|
538
806
|
startedAtMs,
|
|
539
807
|
completedAtMs,
|
|
808
|
+
getTurnCount(),
|
|
540
809
|
);
|
|
541
810
|
|
|
811
|
+
// Phase 2 (#419): Clear active-step var on completion
|
|
812
|
+
clearActiveStep(args.uwf.store, args.threadId);
|
|
813
|
+
|
|
542
814
|
if (extracted === null) {
|
|
543
815
|
log(
|
|
544
816
|
PL_FRONTMATTER_FAIL,
|
|
@@ -623,7 +895,12 @@ export async function executeBrokerStep(args: ExecuteBrokerStepArgs): Promise<Br
|
|
|
623
895
|
|
|
624
896
|
function brokerUsage(result: SendResult): Usage | null {
|
|
625
897
|
// Sumeru's `done` event reports per-exchange usage. Normalize into the
|
|
626
|
-
// engine's Usage shape so `mergeUsage` can sum across retries.
|
|
898
|
+
// engine's Usage shape so `mergeUsage` can sum across retries. A suspended
|
|
899
|
+
// result has no `done` (the discriminated union enforces this narrow) — a
|
|
900
|
+
// timeout carries no usage summary.
|
|
901
|
+
if (result.kind !== "completed") {
|
|
902
|
+
return null;
|
|
903
|
+
}
|
|
627
904
|
const done = result.done;
|
|
628
905
|
if (done === null || typeof done !== "object") {
|
|
629
906
|
return null;
|
package/src/commands/prompt.ts
CHANGED
|
@@ -82,71 +82,57 @@ npm prefix -g # global prefix; bin is <prefix>/bin
|
|
|
82
82
|
|
|
83
83
|
**All checks must pass before continuing.** If you had to modify PATH, verify the change persists by opening a new shell or sourcing your shell config.
|
|
84
84
|
|
|
85
|
-
### Step 1 —
|
|
85
|
+
### Step 1 — Install the CLI and pick an integration path
|
|
86
86
|
|
|
87
|
-
|
|
87
|
+
uwf reaches an LLM/agent backend through one of two paths after Phase 4
|
|
88
|
+
cleanup (#381):
|
|
88
89
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
90
|
+
- **Sumeru gateway via broker (preferred)** — your gateway runs out-of-process
|
|
91
|
+
and listens on \`http://host:port\`. The broker
|
|
92
|
+
(\`@united-workforce/broker\`) calls its \`send\` / \`resume\` / \`poke\` HTTP
|
|
93
|
+
endpoints. Most agents (chat sessions, hosted services, CLI subprocesses you
|
|
94
|
+
wrap) should ship as gateways.
|
|
95
|
+
- **In-process \`createAgent\` adapter** — a local Node binary that runs inside
|
|
96
|
+
the same process as \`uwf\`. Used for tools-bearing OpenAI-compatible loops
|
|
97
|
+
(\`@united-workforce/agent-builtin\` ships \`uwf-builtin\`) or scripted E2E
|
|
98
|
+
fixtures (\`@united-workforce/agent-mock\` ships \`uwf-mock\`).
|
|
98
99
|
|
|
99
|
-
|
|
100
|
-
- **Only claude found** → install \`uwf-claude-code\` adapter
|
|
101
|
-
- **Both found** → ask the user which agent they want uwf to use as default
|
|
102
|
-
- **Neither found** → the user must install at least one agent first:
|
|
103
|
-
- Hermes Agent: https://hermes-agent.nousresearch.com/docs
|
|
104
|
-
- Claude Code: \`npm install -g @anthropic-ai/claude-code\`
|
|
105
|
-
|
|
106
|
-
**Install the uwf CLI and the chosen adapter** using pnpm or npm:
|
|
100
|
+
**Install the uwf CLI** with pnpm or npm:
|
|
107
101
|
|
|
108
102
|
\`\`\`bash
|
|
109
|
-
# CLI (required)
|
|
110
103
|
pnpm add -g @united-workforce/cli # or: npm install -g @united-workforce/cli
|
|
111
|
-
|
|
112
|
-
# Adapter — install the one matching the detected agent:
|
|
113
|
-
pnpm add -g @united-workforce/agent-hermes # or: npm i -g @united-workforce/agent-hermes
|
|
114
|
-
pnpm add -g @united-workforce/agent-claude-code # or: npm i -g @united-workforce/agent-claude-code
|
|
115
104
|
\`\`\`
|
|
116
105
|
|
|
117
|
-
|
|
106
|
+
If you plan to use the in-process built-in adapter, install it too:
|
|
107
|
+
|
|
108
|
+
\`\`\`bash
|
|
109
|
+
pnpm add -g @united-workforce/agent-builtin # ships uwf-builtin
|
|
110
|
+
\`\`\`
|
|
118
111
|
|
|
119
|
-
**
|
|
112
|
+
**Verify that \`uwf\` is available in PATH:**
|
|
120
113
|
|
|
121
114
|
\`\`\`bash
|
|
122
115
|
uwf --version # should print ${CLI_VERSION}
|
|
123
|
-
uwf-hermes --version # or: uwf-claude-code --version
|
|
124
116
|
\`\`\`
|
|
125
117
|
|
|
126
|
-
If
|
|
118
|
+
If \`uwf\` is not found, the global bin directory is not in the current shell's PATH. **You must fix this before continuing:**
|
|
127
119
|
|
|
128
120
|
1. Find where the binary was installed:
|
|
129
121
|
\`\`\`bash
|
|
130
|
-
|
|
131
|
-
npm prefix -g
|
|
122
|
+
pnpm bin -g # for pnpm
|
|
123
|
+
npm prefix -g # for npm (bin is <prefix>/bin)
|
|
132
124
|
\`\`\`
|
|
133
125
|
2. Add the directory to PATH permanently by appending to the user's shell config (e.g. \`~/.bashrc\`, \`~/.zshrc\`, \`~/.profile\`, or fish config):
|
|
134
126
|
\`\`\`bash
|
|
135
127
|
export PATH="<global-bin-dir>:$PATH"
|
|
136
128
|
\`\`\`
|
|
137
|
-
3. Source the updated config or open a new shell, then re-verify the
|
|
138
|
-
|
|
139
|
-
**uwf-hermes** also requires the Hermes ACP plugin. Verify with \`hermes acp --help\`. If not available, install it:
|
|
140
|
-
\`\`\`bash
|
|
141
|
-
# Option A: install into hermes venv (recommended)
|
|
142
|
-
source ~/.hermes/hermes-agent/.venv/bin/activate && pip install hermes-agent[acp]
|
|
129
|
+
3. Source the updated config or open a new shell, then re-verify the command works.
|
|
143
130
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
\`\`\`
|
|
131
|
+
**Legacy note:** The old per-agent CLI binaries and their npm packages have
|
|
132
|
+
been moved to \`legacy-packages/\` and are no longer published. If you
|
|
133
|
+
previously installed any \`@united-workforce/agent-*\` package targeting an
|
|
134
|
+
external chat CLI, uninstall it and either run a Sumeru gateway or use
|
|
135
|
+
\`uwf-builtin\`.
|
|
150
136
|
|
|
151
137
|
### Step 2 — Configure default agent
|
|
152
138
|
|
|
@@ -162,20 +148,20 @@ Or configure non-interactively:
|
|
|
162
148
|
uwf setup --agent <adapter-command>
|
|
163
149
|
\`\`\`
|
|
164
150
|
|
|
165
|
-
**Note:** \`--agent\` takes an alias declared in your \`agents\` map (e.g. \`
|
|
151
|
+
**Note:** \`--agent\` takes an alias declared in your \`agents\` map (e.g. \`builtin\`, \`my-gateway\`) — **not** an adapter command name. Each alias resolves to a \`{host, gateway}\` Sumeru endpoint that the broker contacts over HTTP. \`uwf thread exec --agent\` additionally accepts an inline \`"<host> <gateway>"\` pair for ad-hoc routing.
|
|
166
152
|
|
|
167
153
|
Config is saved to \`~/.uwf/config.yaml\`:
|
|
168
154
|
|
|
169
155
|
\`\`\`yaml
|
|
170
156
|
agents:
|
|
171
|
-
|
|
157
|
+
my-gateway:
|
|
172
158
|
host: http://127.0.0.1:7900
|
|
173
|
-
gateway:
|
|
174
|
-
defaultAgent:
|
|
159
|
+
gateway: my-gateway
|
|
160
|
+
defaultAgent: my-gateway
|
|
175
161
|
agentOverrides: {}
|
|
176
162
|
\`\`\`
|
|
177
163
|
|
|
178
|
-
**LLM configuration** is per-adapter —
|
|
164
|
+
**LLM configuration** is per-adapter — Sumeru gateways own their own provider/model/API-key settings, and in-process adapters store theirs under \`~/.uwf/agents/<name>.yaml\`. The engine config (\`~/.uwf/config.yaml\`) is LLM-free.
|
|
179
165
|
|
|
180
166
|
Verify with \`cat ~/.uwf/config.yaml\`.
|
|
181
167
|
|
|
@@ -261,16 +247,22 @@ npm install -g @united-workforce/cli@latest
|
|
|
261
247
|
uwf --version # should print ${CLI_VERSION}
|
|
262
248
|
\`\`\`
|
|
263
249
|
|
|
264
|
-
Also update
|
|
250
|
+
Also update any in-process adapter you have installed (skip if you only use
|
|
251
|
+
Sumeru gateways via the broker):
|
|
265
252
|
|
|
266
253
|
\`\`\`bash
|
|
267
254
|
# pnpm
|
|
268
|
-
pnpm add -g @united-workforce/agent-
|
|
255
|
+
pnpm add -g @united-workforce/agent-builtin@latest
|
|
269
256
|
|
|
270
257
|
# npm
|
|
271
|
-
npm install -g @united-workforce/agent-
|
|
258
|
+
npm install -g @united-workforce/agent-builtin@latest
|
|
272
259
|
\`\`\`
|
|
273
260
|
|
|
261
|
+
If you previously had any \`@united-workforce/agent-*\` package targeting an
|
|
262
|
+
external chat CLI installed globally, uninstall it — those packages are
|
|
263
|
+
archived under \`legacy-packages/\` as of #381 and no longer published. Reach
|
|
264
|
+
the same backends via a Sumeru gateway in \`~/.uwf/config.yaml\` instead.
|
|
265
|
+
|
|
274
266
|
### Step 2 — Regenerate skills
|
|
275
267
|
|
|
276
268
|
Skill content is bundled with the CLI — always regenerate after upgrading:
|