@llm4ts/flow 0.1.3 → 0.2.0

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/dist/Chat.d.ts CHANGED
@@ -2,10 +2,20 @@ import * as Effect from "effect/Effect";
2
2
  import type { LlmServiceShape } from "@llm4ts/core/LlmService";
3
3
  import { Message } from "@llm4ts/core/Models";
4
4
  import { FlowLlmError } from "./FlowError.ts";
5
+ import { type FlowEventsShape } from "./FlowEvents.ts";
5
6
  export declare const gitOwnershipInstruction = "The flow runtime owns git operations. Do not create or switch branches, commit, push, or open pull requests.";
6
7
  export interface ChatOptions {
7
8
  readonly system?: string;
8
9
  readonly manageGit?: boolean;
10
+ /**
11
+ * When set, every reply whose collected response carries token usage is
12
+ * published as a `TokensUsed` event, which is what `CostTracker` (and
13
+ * therefore cost summaries and budgets) consume. Without it, usage
14
+ * reported by the backend is silently discarded.
15
+ */
16
+ readonly events?: FlowEventsShape;
17
+ /** Request label for published `TokensUsed` events. Default: "chat". */
18
+ readonly agent?: string;
9
19
  }
10
20
  export interface Chat {
11
21
  readonly ask: (prompt: string) => Effect.Effect<string, FlowLlmError>;
@@ -1 +1 @@
1
- {"version":3,"file":"Chat.d.ts","sourceRoot":"","sources":["../src/Chat.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AAGvC,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAA;AAC9D,OAAO,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAE7C,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAA;AAE7C,eAAO,MAAM,uBAAuB,iHAC4E,CAAA;AAEhH,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,CAAA;CAC7B;AAED,MAAM,WAAW,IAAI;IACnB,QAAQ,CAAC,GAAG,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAA;IACrE,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC,CAAA;CACzD;AAcD,eAAO,MAAM,QAAQ,oGA0BnB,CAAA"}
1
+ {"version":3,"file":"Chat.d.ts","sourceRoot":"","sources":["../src/Chat.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AAGvC,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAA;AAC9D,OAAO,EAAE,OAAO,EAAoB,MAAM,qBAAqB,CAAA;AAE/D,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAA;AAC7C,OAAO,EAAc,KAAK,eAAe,EAAE,MAAM,iBAAiB,CAAA;AAElE,eAAO,MAAM,uBAAuB,iHAC4E,CAAA;AAEhH,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,CAAA;IAC5B;;;;;OAKG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,eAAe,CAAA;IACjC,wEAAwE;IACxE,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CACxB;AAaD,MAAM,WAAW,IAAI;IACnB,QAAQ,CAAC,GAAG,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAA;IACrE,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC,CAAA;CACzD;AAcD,eAAO,MAAM,QAAQ,oGA2BnB,CAAA"}
package/dist/Chat.js CHANGED
@@ -4,7 +4,15 @@ import * as Semaphore from "effect/Semaphore";
4
4
  import { Message } from "@llm4ts/core/Models";
5
5
  import { collect } from "@llm4ts/core/Streaming";
6
6
  import { FlowLlmError } from "./FlowError.js";
7
+ import { TokensUsed } from "./FlowEvents.js";
7
8
  export const gitOwnershipInstruction = "The flow runtime owns git operations. Do not create or switch branches, commit, push, or open pull requests.";
9
+ const publishUsage = (options, response) => options.events === undefined || response.usage === undefined
10
+ ? Effect.void
11
+ : options.events.publish(TokensUsed.make({
12
+ agent: options.agent ?? "chat",
13
+ usage: response.usage,
14
+ ...(response.metadata.model === undefined ? {} : { model: response.metadata.model })
15
+ }));
8
16
  const initialHistory = (options) => {
9
17
  const system = options.manageGit === true
10
18
  ? options.system
@@ -22,6 +30,7 @@ export const makeChat = Effect.fn("@llm4ts/flow/Chat.make")(function* (service,
22
30
  const userTurn = Message.make({ role: "User", content: prompt });
23
31
  const messages = [...(yield* Ref.get(history)), userTurn];
24
32
  const response = yield* collect(service.executeStreamWithHistory(messages)).pipe(Effect.mapError(FlowLlmError.from));
33
+ yield* publishUsage(options, response);
25
34
  yield* Ref.set(history, [
26
35
  ...messages,
27
36
  Message.make({ role: "Assistant", content: response.content })
package/dist/Chat.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"Chat.js","sourceRoot":"","sources":["../src/Chat.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AACvC,OAAO,KAAK,GAAG,MAAM,YAAY,CAAA;AACjC,OAAO,KAAK,SAAS,MAAM,kBAAkB,CAAA;AAE7C,OAAO,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAC7C,OAAO,EAAE,OAAO,EAAE,MAAM,wBAAwB,CAAA;AAChD,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAA;AAE7C,MAAM,CAAC,MAAM,uBAAuB,GAClC,8GAA8G,CAAA;AAYhH,MAAM,cAAc,GAAG,CAAC,OAAoB,EAA0B,EAAE;IACtE,MAAM,MAAM,GACV,OAAO,CAAC,SAAS,KAAK,IAAI;QACxB,CAAC,CAAC,OAAO,CAAC,MAAM;QAChB,CAAC,CAAC,CAAC,uBAAuB,EAAE,OAAO,CAAC,MAAM,CAAC;aACtC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,KAAK,SAAS,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC;aACvD,IAAI,CAAC,MAAM,CAAC,CAAA;IACrB,OAAO,MAAM,KAAK,SAAS,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;QAChD,CAAC,CAAC,EAAE;QACJ,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC,CAAA;AACzD,CAAC,CAAA;AAED,MAAM,CAAC,MAAM,QAAQ,GAAG,MAAM,CAAC,EAAE,CAAC,wBAAwB,CAAC,CAAC,QAAQ,CAAC,EACnE,OAAwB,EACxB,UAAuB,EAAE;IAEzB,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC,CAAA;IACxD,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;IAErC,MAAM,QAAQ,GAAG,MAAM,CAAC,EAAE,CAAC,uBAAuB,CAAC,CAAC,QAAQ,CAAC,EAC3D,MAAc;QAEd,MAAM,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CAAA;QAChE,MAAM,QAAQ,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAA;QACzD,MAAM,QAAQ,GAAG,KAAK,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,wBAAwB,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAC9E,MAAM,CAAC,QAAQ,CAAC,YAAY,CAAC,IAAI,CAAC,CACnC,CAAA;QACD,KAAK,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,OAAO,EAAE;YACtB,GAAG,QAAQ;YACX,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,OAAO,EAAE,QAAQ,CAAC,OAAO,EAAE,CAAC;SAC/D,CAAC,CAAA;QACF,OAAO,QAAQ,CAAC,OAAO,CAAA;IACzB,CAAC,CAAC,CAAA;IAEF,OAAO;QACL,GAAG,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;QAClD,QAAQ,EAAE,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC;KAC3B,CAAA;AACH,CAAC,CAAC,CAAA"}
1
+ {"version":3,"file":"Chat.js","sourceRoot":"","sources":["../src/Chat.ts"],"names":[],"mappings":"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"}
package/dist/Flow.d.ts CHANGED
@@ -14,6 +14,13 @@ export interface ImplementPlanOptions {
14
14
  readonly planPath: string;
15
15
  readonly plan: Effect.Effect<Plan, FlowError>;
16
16
  readonly system?: string;
17
+ /**
18
+ * When true, each task gets a fresh Chat (system prompt plus the plan's
19
+ * current render, showing prior tasks' completion status); that task's
20
+ * review-fix rounds share the same Chat. When false or omitted (default),
21
+ * one Chat is shared across every task in the plan.
22
+ */
23
+ readonly chatPerTask?: boolean;
17
24
  readonly reviewers?: ReadonlyArray<Reviewer>;
18
25
  readonly commitMessage?: (plan: Plan, task: Task) => string;
19
26
  readonly checkoutBranch?: boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"Flow.d.ts","sourceRoot":"","sources":["../src/Flow.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AACvC,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAA;AAG9D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAA;AACxD,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,KAAK,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAC1E,OAAO,EAA0B,KAAK,eAAe,EAAE,MAAM,iBAAiB,CAAA;AAC9E,OAAO,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAC3C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAA;AAEtD,OAAO,EAAsC,KAAK,YAAY,EAAE,MAAM,aAAa,CAAA;AACnF,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AAE7C,eAAO,MAAM,YAAY,GAAI,SAAS,gBAAgB,KAAG,eACd,CAAA;AAE3C,eAAO,MAAM,kBAAkB,mHAU7B,CAAA;AAEF,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,KAAK,EAAE,cAAc,CAAA;IAC9B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,SAAS,CAAC,CAAA;IAC7C,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,SAAS,CAAC,EAAE,aAAa,CAAC,QAAQ,CAAC,CAAA;IAC5C,QAAQ,CAAC,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,KAAK,MAAM,CAAA;IAC3D,QAAQ,CAAC,cAAc,CAAC,EAAE,OAAO,CAAA;IACjC,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,CAAC,YAAY,EAAE,SAAS,CAAC,CAAA;IACtD,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,SAAS,CAAC,CAAA;CACjD;AAID,eAAO,MAAM,iBAAiB,2gBAgD5B,CAAA"}
1
+ {"version":3,"file":"Flow.d.ts","sourceRoot":"","sources":["../src/Flow.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AACvC,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAA;AAG9D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAA;AACxD,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,KAAK,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAC1E,OAAO,EAAsC,KAAK,eAAe,EAAE,MAAM,iBAAiB,CAAA;AAC1F,OAAO,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAC3C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAA;AAEtD,OAAO,EAAsC,KAAK,YAAY,EAAE,MAAM,aAAa,CAAA;AACnF,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AAE7C,eAAO,MAAM,YAAY,GAAI,SAAS,gBAAgB,KAAG,eACd,CAAA;AAE3C,eAAO,MAAM,kBAAkB,mHAmB7B,CAAA;AAEF,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,KAAK,EAAE,cAAc,CAAA;IAC9B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,SAAS,CAAC,CAAA;IAC7C,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;IACxB;;;;;OAKG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,OAAO,CAAA;IAC9B,QAAQ,CAAC,SAAS,CAAC,EAAE,aAAa,CAAC,QAAQ,CAAC,CAAA;IAC5C,QAAQ,CAAC,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,KAAK,MAAM,CAAA;IAC3D,QAAQ,CAAC,cAAc,CAAC,EAAE,OAAO,CAAA;IACjC,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,CAAC,YAAY,EAAE,SAAS,CAAC,CAAA;IACtD,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,SAAS,CAAC,CAAA;CACjD;AAOD,eAAO,MAAM,iBAAiB,2gBAoG5B,CAAA"}
package/dist/Flow.js CHANGED
@@ -2,32 +2,81 @@ import * as Effect from "effect/Effect";
2
2
  import { collect } from "@llm4ts/core/Streaming";
3
3
  import { makeChat } from "./Chat.js";
4
4
  import { FlowAborted, FlowLlmError } from "./FlowError.js";
5
- import { AssistantMessage, Info } from "./FlowEvents.js";
5
+ import { AssistantMessage, Info, TokensUsed } from "./FlowEvents.js";
6
6
  import { implementTaskLoop, stage } from "./PlanExecution.js";
7
7
  import { minimalReviewers, reviewAndFixLoop } from "./Review.js";
8
8
  export const flowReviewer = (context) => context.reviewers[0] ?? context.reasoning;
9
9
  export const completeAndPublish = Effect.fn("@llm4ts/flow/Flow.completeAndPublish")(function* (service, events, prompt) {
10
10
  const response = yield* collect(service.executeStream(prompt)).pipe(Effect.mapError(FlowLlmError.from));
11
+ if (response.usage !== undefined) {
12
+ yield* events.publish(TokensUsed.make({
13
+ agent: "assistant",
14
+ usage: response.usage,
15
+ ...(response.metadata.model === undefined ? {} : { model: response.metadata.model })
16
+ }));
17
+ }
11
18
  yield* events.publish(AssistantMessage.make({ text: response.content }));
12
19
  return response.content;
13
20
  });
14
21
  const defaultCommitMessage = (plan, task) => `${plan.epicId}: ${task.title}`;
22
+ const composeSystem = (base, note) => [base, note].filter((part) => part !== undefined && part.length > 0).join("\n\n");
15
23
  export const implementPlanFlow = Effect.fn("@llm4ts/flow/Flow.implementPlan")(function* (context, options) {
16
24
  const plan = yield* options.store.recoverOrCreate(options.planPath, options.plan);
17
25
  if (options.checkoutBranch !== false) {
18
26
  yield* stage(context.events, "branch", context.git.checkoutOrCreate(plan.epicId));
19
27
  }
20
- const coder = yield* makeChat(context.coder, {
21
- ...(options.system === undefined ? {} : { system: options.system })
22
- });
23
- return yield* implementTaskLoop(options.store, context.events, options.planPath, plan, (task) => Effect.gen(function* () {
28
+ let sharedCoder;
29
+ if (options.chatPerTask !== true) {
30
+ sharedCoder = yield* makeChat(context.coder, {
31
+ events: context.events,
32
+ agent: "coder",
33
+ ...(options.system === undefined ? {} : { system: options.system })
34
+ });
35
+ }
36
+ return yield* implementTaskLoop(options.store, context.events, options.planPath, plan, (task, planSoFar) => Effect.gen(function* () {
37
+ // `sharedCoder`'s definedness mirrors `options.chatPerTask !== true`
38
+ // above: when it's set, every task reuses it; when it's undefined,
39
+ // chatPerTask is active and each task builds its own fresh Chat.
40
+ let coder;
41
+ if (sharedCoder !== undefined) {
42
+ coder = sharedCoder;
43
+ }
44
+ else {
45
+ coder = yield* makeChat(context.coder, {
46
+ events: context.events,
47
+ agent: "coder",
48
+ system: composeSystem(options.system, planSoFar.render)
49
+ });
50
+ }
51
+ // `plan.taskPrompt` deliberately reads the frozen `plan` captured at
52
+ // the top of this function: a task prompt only needs that task's own
53
+ // details. `planSoFar`, threaded through by implementTaskLoop, is the
54
+ // single source of truth for completion progress instead.
24
55
  yield* coder.ask(plan.taskPrompt(task));
25
56
  const produced = yield* context.git.diffAll;
26
57
  if (produced.trim().length === 0) {
27
- yield* context.events.publish(Info.make({
28
- message: `task "${task.title}" produced no changes (already satisfied); skipping review and commit`
29
- }));
30
- return;
58
+ // An empty diff is ambiguous: the task may be genuinely satisfied
59
+ // already, or the coder may simply have produced nothing. Ask
60
+ // explicitly instead of inferring from absence; a task that
61
+ // produces no changes and no confirmation fails rather than being
62
+ // silently marked complete.
63
+ const confirmation = yield* coder.ask([
64
+ "Your previous turn produced no file changes.",
65
+ `If the task "${task.title}" is already fully satisfied by the current state of the repository, reply with exactly TASK_ALREADY_SATISFIED and nothing else.`,
66
+ "Otherwise, implement the task now."
67
+ ].join("\n"));
68
+ const afterConfirmation = yield* context.git.diffAll;
69
+ if (afterConfirmation.trim().length === 0) {
70
+ if (confirmation.includes("TASK_ALREADY_SATISFIED")) {
71
+ yield* context.events.publish(Info.make({
72
+ message: `task "${task.title}" confirmed already satisfied; skipping review and commit`
73
+ }));
74
+ return;
75
+ }
76
+ return yield* FlowAborted.make({
77
+ message: `task "${task.title}" produced no changes and did not confirm TASK_ALREADY_SATISFIED; failing instead of marking it complete`
78
+ });
79
+ }
31
80
  }
32
81
  yield* reviewAndFixLoop({
33
82
  reviewers: options.reviewers ?? minimalReviewers,
@@ -46,7 +95,10 @@ export const implementPlanFlow = Effect.fn("@llm4ts/flow/Flow.implementPlan")(fu
46
95
  return yield* FlowAborted.make({
47
96
  message: [
48
97
  `task "${task.title}": the lint gate is still failing after review settled; refusing to commit`,
49
- ...gate.issues.map((issue) => `- [${issue.severity}] ${issue.title}`)
98
+ ...gate.issues.map((issue) => {
99
+ const detail = issue.description.length === 0 ? "" : `\n${issue.description.slice(-2000)}`;
100
+ return `- [${issue.severity}] ${issue.title}${detail}`;
101
+ })
50
102
  ].join("\n")
51
103
  });
52
104
  }
package/dist/Flow.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"Flow.js","sourceRoot":"","sources":["../src/Flow.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"Flow.js","sourceRoot":"","sources":["../src/Flow.ts"],"names":[],"mappings":"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"}
@@ -3,5 +3,5 @@ import { type FlowEventsShape } from "./FlowEvents.ts";
3
3
  import type { Plan, Task } from "./Plan.ts";
4
4
  import type { PlanStoreShape } from "./Persistence.ts";
5
5
  export declare const stage: <A, E, R>(events: FlowEventsShape, name: string, effect: Effect.Effect<A, E, R>) => Effect.Effect<A, E, R>;
6
- export declare const implementTaskLoop: <E, R>(store: PlanStoreShape, events: FlowEventsShape, planPath: string, plan: Plan, perTask: (task: Task) => Effect.Effect<void, E, R>) => Effect.Effect<Plan, import("./FlowError.ts").PersistenceError | import("./FlowError.ts").PlanParseError | import("./FlowError.ts").UnsupportedSchemaVersion | import("./FlowError.ts").WorkspacePathError | import("./FlowError.ts").WorkspaceLimitError | import("./FlowError.ts").WorkspaceIoError | import("./FlowError.ts").FlowAborted | import("./FlowError.ts").ProcessError | import("./FlowError.ts").FlowLlmError | import("./FlowError.ts").FlowCapabilityDenied | import("./FlowError.ts").BudgetExceeded | E, R>;
6
+ export declare const implementTaskLoop: <E, R>(store: PlanStoreShape, events: FlowEventsShape, planPath: string, plan: Plan, perTask: (task: Task, planSoFar: Plan) => Effect.Effect<void, E, R>) => Effect.Effect<Plan, import("./FlowError.ts").PersistenceError | import("./FlowError.ts").PlanParseError | import("./FlowError.ts").UnsupportedSchemaVersion | import("./FlowError.ts").WorkspacePathError | import("./FlowError.ts").WorkspaceLimitError | import("./FlowError.ts").WorkspaceIoError | import("./FlowError.ts").FlowAborted | import("./FlowError.ts").ProcessError | import("./FlowError.ts").FlowLlmError | import("./FlowError.ts").FlowCapabilityDenied | import("./FlowError.ts").BudgetExceeded | E, R>;
7
7
  //# sourceMappingURL=PlanExecution.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"PlanExecution.d.ts","sourceRoot":"","sources":["../src/PlanExecution.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AACvC,OAAO,EAA6C,KAAK,eAAe,EAAE,MAAM,iBAAiB,CAAA;AAEjG,OAAO,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAC3C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAA;AAKtD,eAAO,MAAM,KAAK,GAAI,CAAC,EAAE,CAAC,EAAE,CAAC,EAC3B,QAAQ,eAAe,EACvB,MAAM,MAAM,EACZ,QAAQ,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,KAC7B,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAYrB,CAAA;AAEH,eAAO,MAAM,iBAAiB,GACjB,CAAC,EAAE,CAAC,gGAKG,IAAI,KAAK,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,EAAE,CAAC,CAAC,kgBAYrD,CAAA"}
1
+ {"version":3,"file":"PlanExecution.d.ts","sourceRoot":"","sources":["../src/PlanExecution.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AACvC,OAAO,EAA6C,KAAK,eAAe,EAAE,MAAM,iBAAiB,CAAA;AAEjG,OAAO,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAC3C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAA;AAKtD,eAAO,MAAM,KAAK,GAAI,CAAC,EAAE,CAAC,EAAE,CAAC,EAC3B,QAAQ,eAAe,EACvB,MAAM,MAAM,EACZ,QAAQ,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,KAC7B,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAYrB,CAAA;AAEH,eAAO,MAAM,iBAAiB,GACjB,CAAC,EAAE,CAAC,gGAKG,IAAI,aAAa,IAAI,KAAK,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,EAAE,CAAC,CAAC,kgBAYtE,CAAA"}
@@ -9,7 +9,7 @@ export const implementTaskLoop = Effect.fn("@llm4ts/flow/PlanExecution.implement
9
9
  let current = plan;
10
10
  for (const task of plan.tasks) {
11
11
  if (!task.completed) {
12
- yield* stage(events, task.title, perTask(task));
12
+ yield* stage(events, task.title, perTask(task, current));
13
13
  current = current.complete(task.title);
14
14
  yield* store.save(planPath, current);
15
15
  }
@@ -1 +1 @@
1
- {"version":3,"file":"PlanExecution.js","sourceRoot":"","sources":["../src/PlanExecution.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AACvC,OAAO,EAAE,cAAc,EAAE,WAAW,EAAE,YAAY,EAAwB,MAAM,iBAAiB,CAAA;AAKjG,MAAM,YAAY,GAAG,CAAC,KAAc,EAAU,EAAE,CAC9C,KAAK,YAAY,KAAK,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;AAEpF,MAAM,CAAC,MAAM,KAAK,GAAG,CACnB,MAAuB,EACvB,IAAY,EACZ,MAA8B,EACN,EAAE,CAC1B,MAAM,CAAC,OAAO,CAAC,YAAY,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,CACrD,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EACtB,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,cAAc,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EACtE,MAAM,CAAC,QAAQ,CAAC,CAAC,KAAK,EAAE,EAAE,CACxB,MAAM,CAAC,OAAO,CACZ,WAAW,CAAC,IAAI,CAAC;IACf,KAAK,EAAE,IAAI;IACX,OAAO,EAAE,YAAY,CAAC,KAAK,CAAC;CAC7B,CAAC,CACH,CACF,CACF,CAAA;AAEH,MAAM,CAAC,MAAM,iBAAiB,GAAG,MAAM,CAAC,EAAE,CAAC,8CAA8C,CAAC,CACxF,QAAQ,CAAC,EACP,KAAqB,EACrB,MAAuB,EACvB,QAAgB,EAChB,IAAU,EACV,OAAkD;IAElD,IAAI,OAAO,GAAG,IAAI,CAAA;IAClB,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;QAC9B,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC;YACpB,KAAK,CAAC,CAAC,KAAK,CAAC,MAAM,EAAE,IAAI,CAAC,KAAK,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAA;YAC/C,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;YACtC,KAAK,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAA;QACtC,CAAC;IACH,CAAC;IACD,OAAO,OAAO,CAAA;AAChB,CAAC,CACF,CAAA"}
1
+ {"version":3,"file":"PlanExecution.js","sourceRoot":"","sources":["../src/PlanExecution.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AACvC,OAAO,EAAE,cAAc,EAAE,WAAW,EAAE,YAAY,EAAwB,MAAM,iBAAiB,CAAA;AAKjG,MAAM,YAAY,GAAG,CAAC,KAAc,EAAU,EAAE,CAC9C,KAAK,YAAY,KAAK,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;AAEpF,MAAM,CAAC,MAAM,KAAK,GAAG,CACnB,MAAuB,EACvB,IAAY,EACZ,MAA8B,EACN,EAAE,CAC1B,MAAM,CAAC,OAAO,CAAC,YAAY,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,CACrD,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EACtB,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,cAAc,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EACtE,MAAM,CAAC,QAAQ,CAAC,CAAC,KAAK,EAAE,EAAE,CACxB,MAAM,CAAC,OAAO,CACZ,WAAW,CAAC,IAAI,CAAC;IACf,KAAK,EAAE,IAAI;IACX,OAAO,EAAE,YAAY,CAAC,KAAK,CAAC;CAC7B,CAAC,CACH,CACF,CACF,CAAA;AAEH,MAAM,CAAC,MAAM,iBAAiB,GAAG,MAAM,CAAC,EAAE,CAAC,8CAA8C,CAAC,CACxF,QAAQ,CAAC,EACP,KAAqB,EACrB,MAAuB,EACvB,QAAgB,EAChB,IAAU,EACV,OAAmE;IAEnE,IAAI,OAAO,GAAG,IAAI,CAAA;IAClB,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;QAC9B,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC;YACpB,KAAK,CAAC,CAAC,KAAK,CAAC,MAAM,EAAE,IAAI,CAAC,KAAK,EAAE,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,CAAA;YACxD,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;YACtC,KAAK,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAA;QACtC,CAAC;IACH,CAAC;IACD,OAAO,OAAO,CAAA;AAChB,CAAC,CACF,CAAA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@llm4ts/flow",
3
- "version": "0.1.3",
3
+ "version": "0.2.0",
4
4
  "description": "Effect-native LLM workflow, persistence, review, and repository automation",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -76,7 +76,7 @@
76
76
  "typescript"
77
77
  ],
78
78
  "dependencies": {
79
- "@llm4ts/core": "0.1.3"
79
+ "@llm4ts/core": "0.2.0"
80
80
  },
81
81
  "peerDependencies": {
82
82
  "effect": "^4.0.0-beta.102"
package/src/Chat.ts CHANGED
@@ -2,9 +2,10 @@ import * as Effect from "effect/Effect"
2
2
  import * as Ref from "effect/Ref"
3
3
  import * as Semaphore from "effect/Semaphore"
4
4
  import type { LlmServiceShape } from "@llm4ts/core/LlmService"
5
- import { Message } from "@llm4ts/core/Models"
5
+ import { Message, type LlmResponse } from "@llm4ts/core/Models"
6
6
  import { collect } from "@llm4ts/core/Streaming"
7
7
  import { FlowLlmError } from "./FlowError.ts"
8
+ import { TokensUsed, type FlowEventsShape } from "./FlowEvents.ts"
8
9
 
9
10
  export const gitOwnershipInstruction =
10
11
  "The flow runtime owns git operations. Do not create or switch branches, commit, push, or open pull requests."
@@ -12,8 +13,28 @@ export const gitOwnershipInstruction =
12
13
  export interface ChatOptions {
13
14
  readonly system?: string
14
15
  readonly manageGit?: boolean
16
+ /**
17
+ * When set, every reply whose collected response carries token usage is
18
+ * published as a `TokensUsed` event, which is what `CostTracker` (and
19
+ * therefore cost summaries and budgets) consume. Without it, usage
20
+ * reported by the backend is silently discarded.
21
+ */
22
+ readonly events?: FlowEventsShape
23
+ /** Request label for published `TokensUsed` events. Default: "chat". */
24
+ readonly agent?: string
15
25
  }
16
26
 
27
+ const publishUsage = (options: ChatOptions, response: LlmResponse): Effect.Effect<void> =>
28
+ options.events === undefined || response.usage === undefined
29
+ ? Effect.void
30
+ : options.events.publish(
31
+ TokensUsed.make({
32
+ agent: options.agent ?? "chat",
33
+ usage: response.usage,
34
+ ...(response.metadata.model === undefined ? {} : { model: response.metadata.model })
35
+ })
36
+ )
37
+
17
38
  export interface Chat {
18
39
  readonly ask: (prompt: string) => Effect.Effect<string, FlowLlmError>
19
40
  readonly messages: Effect.Effect<ReadonlyArray<Message>>
@@ -46,6 +67,7 @@ export const makeChat = Effect.fn("@llm4ts/flow/Chat.make")(function* (
46
67
  const response = yield* collect(service.executeStreamWithHistory(messages)).pipe(
47
68
  Effect.mapError(FlowLlmError.from)
48
69
  )
70
+ yield* publishUsage(options, response)
49
71
  yield* Ref.set(history, [
50
72
  ...messages,
51
73
  Message.make({ role: "Assistant", content: response.content })
package/src/Flow.ts CHANGED
@@ -1,10 +1,10 @@
1
1
  import * as Effect from "effect/Effect"
2
2
  import type { LlmServiceShape } from "@llm4ts/core/LlmService"
3
3
  import { collect } from "@llm4ts/core/Streaming"
4
- import { makeChat } from "./Chat.ts"
4
+ import { makeChat, type Chat } from "./Chat.ts"
5
5
  import type { FlowContextShape } from "./FlowContext.ts"
6
6
  import { FlowAborted, FlowLlmError, type FlowError } from "./FlowError.ts"
7
- import { AssistantMessage, Info, type FlowEventsShape } from "./FlowEvents.ts"
7
+ import { AssistantMessage, Info, TokensUsed, type FlowEventsShape } from "./FlowEvents.ts"
8
8
  import type { Plan, Task } from "./Plan.ts"
9
9
  import type { PlanStoreShape } from "./Persistence.ts"
10
10
  import { implementTaskLoop, stage } from "./PlanExecution.ts"
@@ -22,6 +22,15 @@ export const completeAndPublish = Effect.fn("@llm4ts/flow/Flow.completeAndPublis
22
22
  const response = yield* collect(service.executeStream(prompt)).pipe(
23
23
  Effect.mapError(FlowLlmError.from)
24
24
  )
25
+ if (response.usage !== undefined) {
26
+ yield* events.publish(
27
+ TokensUsed.make({
28
+ agent: "assistant",
29
+ usage: response.usage,
30
+ ...(response.metadata.model === undefined ? {} : { model: response.metadata.model })
31
+ })
32
+ )
33
+ }
25
34
  yield* events.publish(AssistantMessage.make({ text: response.content }))
26
35
  return response.content
27
36
  })
@@ -31,6 +40,13 @@ export interface ImplementPlanOptions {
31
40
  readonly planPath: string
32
41
  readonly plan: Effect.Effect<Plan, FlowError>
33
42
  readonly system?: string
43
+ /**
44
+ * When true, each task gets a fresh Chat (system prompt plus the plan's
45
+ * current render, showing prior tasks' completion status); that task's
46
+ * review-fix rounds share the same Chat. When false or omitted (default),
47
+ * one Chat is shared across every task in the plan.
48
+ */
49
+ readonly chatPerTask?: boolean
34
50
  readonly reviewers?: ReadonlyArray<Reviewer>
35
51
  readonly commitMessage?: (plan: Plan, task: Task) => string
36
52
  readonly checkoutBranch?: boolean
@@ -41,6 +57,9 @@ export interface ImplementPlanOptions {
41
57
 
42
58
  const defaultCommitMessage = (plan: Plan, task: Task): string => `${plan.epicId}: ${task.title}`
43
59
 
60
+ const composeSystem = (base: string | undefined, note: string): string =>
61
+ [base, note].filter((part): part is string => part !== undefined && part.length > 0).join("\n\n")
62
+
44
63
  export const implementPlanFlow = Effect.fn("@llm4ts/flow/Flow.implementPlan")(function* (
45
64
  context: FlowContextShape,
46
65
  options: ImplementPlanOptions
@@ -49,44 +68,96 @@ export const implementPlanFlow = Effect.fn("@llm4ts/flow/Flow.implementPlan")(fu
49
68
  if (options.checkoutBranch !== false) {
50
69
  yield* stage(context.events, "branch", context.git.checkoutOrCreate(plan.epicId))
51
70
  }
52
- const coder = yield* makeChat(context.coder, {
53
- ...(options.system === undefined ? {} : { system: options.system })
54
- })
55
- return yield* implementTaskLoop(options.store, context.events, options.planPath, plan, (task) =>
56
- Effect.gen(function* () {
57
- yield* coder.ask(plan.taskPrompt(task))
58
- const produced = yield* context.git.diffAll
59
- if (produced.trim().length === 0) {
60
- yield* context.events.publish(
61
- Info.make({
62
- message: `task "${task.title}" produced no changes (already satisfied); skipping review and commit`
71
+ let sharedCoder: Chat | undefined
72
+ if (options.chatPerTask !== true) {
73
+ sharedCoder = yield* makeChat(context.coder, {
74
+ events: context.events,
75
+ agent: "coder",
76
+ ...(options.system === undefined ? {} : { system: options.system })
77
+ })
78
+ }
79
+
80
+ return yield* implementTaskLoop(
81
+ options.store,
82
+ context.events,
83
+ options.planPath,
84
+ plan,
85
+ (task, planSoFar) =>
86
+ Effect.gen(function* () {
87
+ // `sharedCoder`'s definedness mirrors `options.chatPerTask !== true`
88
+ // above: when it's set, every task reuses it; when it's undefined,
89
+ // chatPerTask is active and each task builds its own fresh Chat.
90
+ let coder: Chat
91
+ if (sharedCoder !== undefined) {
92
+ coder = sharedCoder
93
+ } else {
94
+ coder = yield* makeChat(context.coder, {
95
+ events: context.events,
96
+ agent: "coder",
97
+ system: composeSystem(options.system, planSoFar.render)
63
98
  })
64
- )
65
- return
66
- }
67
- yield* reviewAndFixLoop({
68
- reviewers: options.reviewers ?? minimalReviewers,
69
- reviewerService: flowReviewer(context),
70
- coder,
71
- taskTitle: task.title,
72
- currentDiff: context.git.diffAll,
73
- events: context.events,
74
- ...(options.maxRounds === undefined ? {} : { maxRounds: options.maxRounds }),
75
- ...(options.lint === undefined ? {} : { lint: options.lint }),
76
- ...(options.format === undefined ? {} : { format: options.format })
77
- })
78
- if (options.lint !== undefined) {
79
- const gate = yield* options.lint
80
- if (!gate.isClean) {
81
- return yield* FlowAborted.make({
82
- message: [
83
- `task "${task.title}": the lint gate is still failing after review settled; refusing to commit`,
84
- ...gate.issues.map((issue) => `- [${issue.severity}] ${issue.title}`)
99
+ }
100
+ // `plan.taskPrompt` deliberately reads the frozen `plan` captured at
101
+ // the top of this function: a task prompt only needs that task's own
102
+ // details. `planSoFar`, threaded through by implementTaskLoop, is the
103
+ // single source of truth for completion progress instead.
104
+ yield* coder.ask(plan.taskPrompt(task))
105
+ const produced = yield* context.git.diffAll
106
+ if (produced.trim().length === 0) {
107
+ // An empty diff is ambiguous: the task may be genuinely satisfied
108
+ // already, or the coder may simply have produced nothing. Ask
109
+ // explicitly instead of inferring from absence; a task that
110
+ // produces no changes and no confirmation fails rather than being
111
+ // silently marked complete.
112
+ const confirmation = yield* coder.ask(
113
+ [
114
+ "Your previous turn produced no file changes.",
115
+ `If the task "${task.title}" is already fully satisfied by the current state of the repository, reply with exactly TASK_ALREADY_SATISFIED and nothing else.`,
116
+ "Otherwise, implement the task now."
85
117
  ].join("\n")
86
- })
118
+ )
119
+ const afterConfirmation = yield* context.git.diffAll
120
+ if (afterConfirmation.trim().length === 0) {
121
+ if (confirmation.includes("TASK_ALREADY_SATISFIED")) {
122
+ yield* context.events.publish(
123
+ Info.make({
124
+ message: `task "${task.title}" confirmed already satisfied; skipping review and commit`
125
+ })
126
+ )
127
+ return
128
+ }
129
+ return yield* FlowAborted.make({
130
+ message: `task "${task.title}" produced no changes and did not confirm TASK_ALREADY_SATISFIED; failing instead of marking it complete`
131
+ })
132
+ }
87
133
  }
88
- }
89
- yield* context.git.commitAll((options.commitMessage ?? defaultCommitMessage)(plan, task))
90
- })
134
+ yield* reviewAndFixLoop({
135
+ reviewers: options.reviewers ?? minimalReviewers,
136
+ reviewerService: flowReviewer(context),
137
+ coder,
138
+ taskTitle: task.title,
139
+ currentDiff: context.git.diffAll,
140
+ events: context.events,
141
+ ...(options.maxRounds === undefined ? {} : { maxRounds: options.maxRounds }),
142
+ ...(options.lint === undefined ? {} : { lint: options.lint }),
143
+ ...(options.format === undefined ? {} : { format: options.format })
144
+ })
145
+ if (options.lint !== undefined) {
146
+ const gate = yield* options.lint
147
+ if (!gate.isClean) {
148
+ return yield* FlowAborted.make({
149
+ message: [
150
+ `task "${task.title}": the lint gate is still failing after review settled; refusing to commit`,
151
+ ...gate.issues.map((issue) => {
152
+ const detail =
153
+ issue.description.length === 0 ? "" : `\n${issue.description.slice(-2000)}`
154
+ return `- [${issue.severity}] ${issue.title}${detail}`
155
+ })
156
+ ].join("\n")
157
+ })
158
+ }
159
+ }
160
+ yield* context.git.commitAll((options.commitMessage ?? defaultCommitMessage)(plan, task))
161
+ })
91
162
  )
92
163
  })
@@ -31,12 +31,12 @@ export const implementTaskLoop = Effect.fn("@llm4ts/flow/PlanExecution.implement
31
31
  events: FlowEventsShape,
32
32
  planPath: string,
33
33
  plan: Plan,
34
- perTask: (task: Task) => Effect.Effect<void, E, R>
34
+ perTask: (task: Task, planSoFar: Plan) => Effect.Effect<void, E, R>
35
35
  ): Effect.fn.Return<Plan, E | FlowError, R> {
36
36
  let current = plan
37
37
  for (const task of plan.tasks) {
38
38
  if (!task.completed) {
39
- yield* stage(events, task.title, perTask(task))
39
+ yield* stage(events, task.title, perTask(task, current))
40
40
  current = current.complete(task.title)
41
41
  yield* store.save(planPath, current)
42
42
  }