@pmelab/gtd 51.0.0 → 51.0.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/.claude-plugin/plugin.json +1 -1
- package/dist/gtd.bundle.mjs +26 -25
- package/dist/testkit.mjs +26 -25
- package/package.json +1 -1
- package/src/edge/Edge.ts +1 -30
- package/src/replay/Replay.test.ts +4 -8
- package/src/replay/Replay.ts +35 -3
- package/src/replay/index.ts +7 -1
package/dist/gtd.bundle.mjs
CHANGED
|
@@ -38078,6 +38078,28 @@ const memoryScopeOf = (name) => {
|
|
|
38078
38078
|
const dot = name.lastIndexOf(".");
|
|
38079
38079
|
return dot === -1 ? "" : name.slice(0, dot);
|
|
38080
38080
|
};
|
|
38081
|
+
/** Is `name` inside `scope`'s subtree? The root scope `""` holds everything. */
|
|
38082
|
+
const inScope = (name, scope) => scope === "" || name === scope || name.startsWith(`${scope}.`);
|
|
38083
|
+
/**
|
|
38084
|
+
* Where the current unbroken run of rests inside `scope`'s subtree began —
|
|
38085
|
+
* a dip into a descendant scope does not break it, a sibling or ancestor
|
|
38086
|
+
* does, and so does a fresh `scope()` call of the same scope.
|
|
38087
|
+
*/
|
|
38088
|
+
const scopeRunStart = (trace, scope) => {
|
|
38089
|
+
let start = -1;
|
|
38090
|
+
for (let k = 0; k < trace.length; k++) {
|
|
38091
|
+
if (!inScope(trace[k].memoryScope, scope)) continue;
|
|
38092
|
+
const previous = trace[k - 1];
|
|
38093
|
+
if (!(previous !== void 0 && inScope(previous.memoryScope, scope) && scopeCallOf(previous, scope) === scopeCallOf(trace[k], scope))) start = k;
|
|
38094
|
+
}
|
|
38095
|
+
return start;
|
|
38096
|
+
};
|
|
38097
|
+
/** The innermost `scope()` call enclosing `scope` at `step` — a new call is a new conversation. */
|
|
38098
|
+
const scopeCallOf = (step, scope) => {
|
|
38099
|
+
let call;
|
|
38100
|
+
for (const entry of step.scopeCalls) if (inScope(scope, entry.prefix)) call = entry.call;
|
|
38101
|
+
return call;
|
|
38102
|
+
};
|
|
38081
38103
|
var Stop = class extends Error {};
|
|
38082
38104
|
const short = (hash) => hash.slice(0, 7);
|
|
38083
38105
|
/** One step's changes; content is read only when a flow asks for it. */
|
|
@@ -38337,12 +38359,13 @@ const replay = async (input) => {
|
|
|
38337
38359
|
skills: step.skills,
|
|
38338
38360
|
access: unfoldedAccess.get(step)
|
|
38339
38361
|
};
|
|
38340
|
-
const
|
|
38362
|
+
const conversation = `${step.memoryScope}#${scopeRunStart(trace, step.memoryScope)}`;
|
|
38363
|
+
const seen = personas.get(conversation);
|
|
38341
38364
|
if (seen === void 0) {
|
|
38342
|
-
personas.set(
|
|
38365
|
+
personas.set(conversation, identity);
|
|
38343
38366
|
return;
|
|
38344
38367
|
}
|
|
38345
|
-
return samePersona(seen, identity) ? void 0 : `gtd: "${step.name}" runs with a different model, system prompt, skills or file access than an earlier agent step in memory scope "${step.memoryScope || "root"}" —
|
|
38368
|
+
return samePersona(seen, identity) ? void 0 : `gtd: "${step.name}" runs with a different model, system prompt, skills or file access than an earlier agent step in its conversation in memory scope "${step.memoryScope || "root"}" — a conversation has one identity`;
|
|
38346
38369
|
};
|
|
38347
38370
|
const DIVERGED = "the workflow changed under this process; run `gtd abandon` to start over";
|
|
38348
38371
|
const blockerAt = (step) => {
|
|
@@ -40110,28 +40133,6 @@ const stepDefOf = (step) => {
|
|
|
40110
40133
|
case "human": return messageDef(common, request, step);
|
|
40111
40134
|
}
|
|
40112
40135
|
};
|
|
40113
|
-
/** Is `name` inside `scope`'s subtree? The root scope `""` holds everything. */
|
|
40114
|
-
const inScope = (name, scope) => scope === "" || name === scope || name.startsWith(`${scope}.`);
|
|
40115
|
-
/**
|
|
40116
|
-
* Where the current unbroken run of rests inside `scope`'s subtree began —
|
|
40117
|
-
* a dip into a descendant scope does not break it, a sibling or ancestor
|
|
40118
|
-
* does, and so does a fresh `scope()` call of the same scope.
|
|
40119
|
-
*/
|
|
40120
|
-
const scopeRunStart = (trace, scope) => {
|
|
40121
|
-
let start = -1;
|
|
40122
|
-
for (let k = 0; k < trace.length; k++) {
|
|
40123
|
-
if (!inScope(trace[k].memoryScope, scope)) continue;
|
|
40124
|
-
const previous = trace[k - 1];
|
|
40125
|
-
if (!(previous !== void 0 && inScope(previous.memoryScope, scope) && scopeCallOf(previous, scope) === scopeCallOf(trace[k], scope))) start = k;
|
|
40126
|
-
}
|
|
40127
|
-
return start;
|
|
40128
|
-
};
|
|
40129
|
-
/** The innermost `scope()` call enclosing `scope` at `step` — a new call is a new conversation. */
|
|
40130
|
-
const scopeCallOf = (step, scope) => {
|
|
40131
|
-
let call;
|
|
40132
|
-
for (const entry of step.scopeCalls) if (inScope(scope, entry.prefix)) call = entry.call;
|
|
40133
|
-
return call;
|
|
40134
|
-
};
|
|
40135
40136
|
const ROOT_MEMORY_SCOPE_NAME = "root";
|
|
40136
40137
|
/**
|
|
40137
40138
|
* An agent rest's memory key, `<scope>#<hash7>`: the hash is the commit the
|
package/dist/testkit.mjs
CHANGED
|
@@ -7124,6 +7124,28 @@ const memoryScopeOf = (name) => {
|
|
|
7124
7124
|
const dot = name.lastIndexOf(".");
|
|
7125
7125
|
return dot === -1 ? "" : name.slice(0, dot);
|
|
7126
7126
|
};
|
|
7127
|
+
/** Is `name` inside `scope`'s subtree? The root scope `""` holds everything. */
|
|
7128
|
+
const inScope = (name, scope) => scope === "" || name === scope || name.startsWith(`${scope}.`);
|
|
7129
|
+
/**
|
|
7130
|
+
* Where the current unbroken run of rests inside `scope`'s subtree began —
|
|
7131
|
+
* a dip into a descendant scope does not break it, a sibling or ancestor
|
|
7132
|
+
* does, and so does a fresh `scope()` call of the same scope.
|
|
7133
|
+
*/
|
|
7134
|
+
const scopeRunStart = (trace, scope) => {
|
|
7135
|
+
let start = -1;
|
|
7136
|
+
for (let k = 0; k < trace.length; k++) {
|
|
7137
|
+
if (!inScope(trace[k].memoryScope, scope)) continue;
|
|
7138
|
+
const previous = trace[k - 1];
|
|
7139
|
+
if (!(previous !== void 0 && inScope(previous.memoryScope, scope) && scopeCallOf(previous, scope) === scopeCallOf(trace[k], scope))) start = k;
|
|
7140
|
+
}
|
|
7141
|
+
return start;
|
|
7142
|
+
};
|
|
7143
|
+
/** The innermost `scope()` call enclosing `scope` at `step` — a new call is a new conversation. */
|
|
7144
|
+
const scopeCallOf = (step, scope) => {
|
|
7145
|
+
let call;
|
|
7146
|
+
for (const entry of step.scopeCalls) if (inScope(scope, entry.prefix)) call = entry.call;
|
|
7147
|
+
return call;
|
|
7148
|
+
};
|
|
7127
7149
|
var Stop = class extends Error {};
|
|
7128
7150
|
const short = (hash) => hash.slice(0, 7);
|
|
7129
7151
|
/** One step's changes; content is read only when a flow asks for it. */
|
|
@@ -7383,12 +7405,13 @@ const replay = async (input) => {
|
|
|
7383
7405
|
skills: step.skills,
|
|
7384
7406
|
access: unfoldedAccess.get(step)
|
|
7385
7407
|
};
|
|
7386
|
-
const
|
|
7408
|
+
const conversation = `${step.memoryScope}#${scopeRunStart(trace, step.memoryScope)}`;
|
|
7409
|
+
const seen = personas.get(conversation);
|
|
7387
7410
|
if (seen === void 0) {
|
|
7388
|
-
personas.set(
|
|
7411
|
+
personas.set(conversation, identity);
|
|
7389
7412
|
return;
|
|
7390
7413
|
}
|
|
7391
|
-
return samePersona(seen, identity) ? void 0 : `gtd: "${step.name}" runs with a different model, system prompt, skills or file access than an earlier agent step in memory scope "${step.memoryScope || "root"}" —
|
|
7414
|
+
return samePersona(seen, identity) ? void 0 : `gtd: "${step.name}" runs with a different model, system prompt, skills or file access than an earlier agent step in its conversation in memory scope "${step.memoryScope || "root"}" — a conversation has one identity`;
|
|
7392
7415
|
};
|
|
7393
7416
|
const DIVERGED = "the workflow changed under this process; run `gtd abandon` to start over";
|
|
7394
7417
|
const blockerAt = (step) => {
|
|
@@ -9145,28 +9168,6 @@ const stepDefOf = (step) => {
|
|
|
9145
9168
|
case "human": return messageDef(common, request, step);
|
|
9146
9169
|
}
|
|
9147
9170
|
};
|
|
9148
|
-
/** Is `name` inside `scope`'s subtree? The root scope `""` holds everything. */
|
|
9149
|
-
const inScope = (name, scope) => scope === "" || name === scope || name.startsWith(`${scope}.`);
|
|
9150
|
-
/**
|
|
9151
|
-
* Where the current unbroken run of rests inside `scope`'s subtree began —
|
|
9152
|
-
* a dip into a descendant scope does not break it, a sibling or ancestor
|
|
9153
|
-
* does, and so does a fresh `scope()` call of the same scope.
|
|
9154
|
-
*/
|
|
9155
|
-
const scopeRunStart = (trace, scope) => {
|
|
9156
|
-
let start = -1;
|
|
9157
|
-
for (let k = 0; k < trace.length; k++) {
|
|
9158
|
-
if (!inScope(trace[k].memoryScope, scope)) continue;
|
|
9159
|
-
const previous = trace[k - 1];
|
|
9160
|
-
if (!(previous !== void 0 && inScope(previous.memoryScope, scope) && scopeCallOf(previous, scope) === scopeCallOf(trace[k], scope))) start = k;
|
|
9161
|
-
}
|
|
9162
|
-
return start;
|
|
9163
|
-
};
|
|
9164
|
-
/** The innermost `scope()` call enclosing `scope` at `step` — a new call is a new conversation. */
|
|
9165
|
-
const scopeCallOf = (step, scope) => {
|
|
9166
|
-
let call;
|
|
9167
|
-
for (const entry of step.scopeCalls) if (inScope(scope, entry.prefix)) call = entry.call;
|
|
9168
|
-
return call;
|
|
9169
|
-
};
|
|
9170
9171
|
const ROOT_MEMORY_SCOPE_NAME = "root";
|
|
9171
9172
|
/**
|
|
9172
9173
|
* An agent rest's memory key, `<scope>#<hash7>`: the hash is the commit the
|
package/package.json
CHANGED
package/src/edge/Edge.ts
CHANGED
|
@@ -37,6 +37,7 @@ import {
|
|
|
37
37
|
isRecordableApproval,
|
|
38
38
|
parseCommitMessage,
|
|
39
39
|
replay,
|
|
40
|
+
scopeRunStart,
|
|
40
41
|
sliceRanges,
|
|
41
42
|
treeFromRecord,
|
|
42
43
|
type EpisodeCommit,
|
|
@@ -791,36 +792,6 @@ const stepDefOf = (step: ReachedStep): StepDef => {
|
|
|
791
792
|
}
|
|
792
793
|
}
|
|
793
794
|
|
|
794
|
-
/** Is `name` inside `scope`'s subtree? The root scope `""` holds everything. */
|
|
795
|
-
const inScope = (name: string, scope: string): boolean =>
|
|
796
|
-
scope === "" || name === scope || name.startsWith(`${scope}.`)
|
|
797
|
-
|
|
798
|
-
/**
|
|
799
|
-
* Where the current unbroken run of rests inside `scope`'s subtree began —
|
|
800
|
-
* a dip into a descendant scope does not break it, a sibling or ancestor
|
|
801
|
-
* does, and so does a fresh `scope()` call of the same scope.
|
|
802
|
-
*/
|
|
803
|
-
const scopeRunStart = (trace: readonly ReachedStep[], scope: string): number => {
|
|
804
|
-
let start = -1
|
|
805
|
-
for (let k = 0; k < trace.length; k++) {
|
|
806
|
-
if (!inScope(trace[k]!.memoryScope, scope)) continue
|
|
807
|
-
const previous = trace[k - 1]
|
|
808
|
-
const unbroken =
|
|
809
|
-
previous !== undefined &&
|
|
810
|
-
inScope(previous.memoryScope, scope) &&
|
|
811
|
-
scopeCallOf(previous, scope) === scopeCallOf(trace[k]!, scope)
|
|
812
|
-
if (!unbroken) start = k
|
|
813
|
-
}
|
|
814
|
-
return start
|
|
815
|
-
}
|
|
816
|
-
|
|
817
|
-
/** The innermost `scope()` call enclosing `scope` at `step` — a new call is a new conversation. */
|
|
818
|
-
const scopeCallOf = (step: ReachedStep, scope: string): number | undefined => {
|
|
819
|
-
let call: number | undefined
|
|
820
|
-
for (const entry of step.scopeCalls) if (inScope(scope, entry.prefix)) call = entry.call
|
|
821
|
-
return call
|
|
822
|
-
}
|
|
823
|
-
|
|
824
795
|
// Shown for the root scope, which has no name of its own.
|
|
825
796
|
const ROOT_MEMORY_SCOPE_NAME = "root"
|
|
826
797
|
|
|
@@ -940,7 +940,7 @@ describe("replay: skills resolution", () => {
|
|
|
940
940
|
expect(await wireSkills(inScope({ name: "a" }, step), { skills: { a: [] } })).toBeUndefined()
|
|
941
941
|
})
|
|
942
942
|
|
|
943
|
-
it("
|
|
943
|
+
it("runs two scope() calls of one memory scope with different lists as two conversations", async () => {
|
|
944
944
|
const flow: Flow = async () => {
|
|
945
945
|
await scope({ name: "a", skills: ["one"] }, async () => {
|
|
946
946
|
await agent("first", "prompt")
|
|
@@ -959,9 +959,7 @@ describe("replay: skills resolution", () => {
|
|
|
959
959
|
start: hashOf(0),
|
|
960
960
|
budgetBytes: 1024,
|
|
961
961
|
})
|
|
962
|
-
expect(outcome.kind
|
|
963
|
-
"different model, system prompt, skills or file access",
|
|
964
|
-
)
|
|
962
|
+
expect(outcome.kind).toBe("rest")
|
|
965
963
|
})
|
|
966
964
|
})
|
|
967
965
|
|
|
@@ -1084,7 +1082,7 @@ describe("replay: access resolution", () => {
|
|
|
1084
1082
|
expect(outcome.rest.access).toEqual({ read: null, write: ["src/a.ts"] })
|
|
1085
1083
|
})
|
|
1086
1084
|
|
|
1087
|
-
it("
|
|
1085
|
+
it("runs two scope() calls of one memory scope with different access as two conversations", async () => {
|
|
1088
1086
|
const flow: Flow = async () => {
|
|
1089
1087
|
await scope({ name: "a", access: { write: ["one"] } }, () => agent("first", "prompt"))
|
|
1090
1088
|
await scope({ name: "a", access: { write: ["two"] } }, () => agent("second", "prompt"))
|
|
@@ -1092,9 +1090,7 @@ describe("replay: access resolution", () => {
|
|
|
1092
1090
|
const history = new History()
|
|
1093
1091
|
history.land("agent", "a.first", 1, "a.second", { "x.txt": "x" })
|
|
1094
1092
|
const outcome = await restOf(flow, {}, history)
|
|
1095
|
-
expect(outcome.kind
|
|
1096
|
-
"different model, system prompt, skills or file access",
|
|
1097
|
-
)
|
|
1093
|
+
expect(outcome.kind).toBe("rest")
|
|
1098
1094
|
})
|
|
1099
1095
|
|
|
1100
1096
|
it("does not conflict two steps that differ only in steering file", async () => {
|
package/src/replay/Replay.ts
CHANGED
|
@@ -139,6 +139,36 @@ const memoryScopeOf = (name: string): string => {
|
|
|
139
139
|
return dot === -1 ? "" : name.slice(0, dot)
|
|
140
140
|
}
|
|
141
141
|
|
|
142
|
+
/** Is `name` inside `scope`'s subtree? The root scope `""` holds everything. */
|
|
143
|
+
const inScope = (name: string, scope: string): boolean =>
|
|
144
|
+
scope === "" || name === scope || name.startsWith(`${scope}.`)
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Where the current unbroken run of rests inside `scope`'s subtree began —
|
|
148
|
+
* a dip into a descendant scope does not break it, a sibling or ancestor
|
|
149
|
+
* does, and so does a fresh `scope()` call of the same scope.
|
|
150
|
+
*/
|
|
151
|
+
export const scopeRunStart = (trace: readonly ReachedStep[], scope: string): number => {
|
|
152
|
+
let start = -1
|
|
153
|
+
for (let k = 0; k < trace.length; k++) {
|
|
154
|
+
if (!inScope(trace[k]!.memoryScope, scope)) continue
|
|
155
|
+
const previous = trace[k - 1]
|
|
156
|
+
const unbroken =
|
|
157
|
+
previous !== undefined &&
|
|
158
|
+
inScope(previous.memoryScope, scope) &&
|
|
159
|
+
scopeCallOf(previous, scope) === scopeCallOf(trace[k]!, scope)
|
|
160
|
+
if (!unbroken) start = k
|
|
161
|
+
}
|
|
162
|
+
return start
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/** The innermost `scope()` call enclosing `scope` at `step` — a new call is a new conversation. */
|
|
166
|
+
const scopeCallOf = (step: ReachedStep, scope: string): number | undefined => {
|
|
167
|
+
let call: number | undefined
|
|
168
|
+
for (const entry of step.scopeCalls) if (inScope(scope, entry.prefix)) call = entry.call
|
|
169
|
+
return call
|
|
170
|
+
}
|
|
171
|
+
|
|
142
172
|
class Stop extends Error {}
|
|
143
173
|
|
|
144
174
|
const short = (hash: string): string => hash.slice(0, 7)
|
|
@@ -505,14 +535,16 @@ export const replay = async (input: ReplayInput): Promise<ReplayOutcome> => {
|
|
|
505
535
|
skills: step.skills,
|
|
506
536
|
access: unfoldedAccess.get(step),
|
|
507
537
|
}
|
|
508
|
-
|
|
538
|
+
// Keyed like the memory key: a fresh scope() call is a fresh conversation, free to differ.
|
|
539
|
+
const conversation = `${step.memoryScope}#${scopeRunStart(trace, step.memoryScope)}`
|
|
540
|
+
const seen = personas.get(conversation)
|
|
509
541
|
if (seen === undefined) {
|
|
510
|
-
personas.set(
|
|
542
|
+
personas.set(conversation, identity)
|
|
511
543
|
return undefined
|
|
512
544
|
}
|
|
513
545
|
return samePersona(seen, identity)
|
|
514
546
|
? undefined
|
|
515
|
-
: `gtd: "${step.name}" runs with a different model, system prompt, skills or file access than an earlier agent step in memory scope "${step.memoryScope || "root"}" —
|
|
547
|
+
: `gtd: "${step.name}" runs with a different model, system prompt, skills or file access than an earlier agent step in its conversation in memory scope "${step.memoryScope || "root"}" — a conversation has one identity`
|
|
516
548
|
}
|
|
517
549
|
|
|
518
550
|
const DIVERGED = "the workflow changed under this process; run `gtd abandon` to start over"
|
package/src/replay/index.ts
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export {
|
|
2
|
+
replay,
|
|
3
|
+
scopeRunStart,
|
|
4
|
+
type EpisodeCommit,
|
|
5
|
+
type ReachedStep,
|
|
6
|
+
type ReplayOutcome,
|
|
7
|
+
} from "./Replay.js"
|
|
2
8
|
export { treeFromRecord, type TreeView } from "./Tree.js"
|
|
3
9
|
export {
|
|
4
10
|
formatCommitMessage,
|