@akagilnc/pi-workflow-roles 0.1.1840 → 0.1.1854
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/in-process-session.js +15 -1
- package/dist/navigator-attendance.js +9 -33
- package/dist/sitian-record-entry.js +15 -1
- package/extensions/role-runtime.ts +0 -4
- package/package.json +1 -1
- package/src/in-process-session.ts +40 -3
- package/src/navigator-attendance.ts +13 -39
- package/src/reviewer-role.ts +1 -1
- package/src/role-runtime.ts +1 -1
- package/src/sitian-record-entry.ts +29 -1
|
@@ -5,17 +5,31 @@
|
|
|
5
5
|
* does not statically reach @earendil-works/pi-coding-agent.
|
|
6
6
|
*/
|
|
7
7
|
import { createAgentSession, DefaultResourceLoader, SettingsManager, } from "@earendil-works/pi-coding-agent";
|
|
8
|
+
import { createRecordSession } from "./sitian-record-entry.js";
|
|
9
|
+
function resolveSessionManager(options) {
|
|
10
|
+
if (options.sessionManager !== undefined)
|
|
11
|
+
return options.sessionManager;
|
|
12
|
+
return createRecordSession({
|
|
13
|
+
cwd: options.cwd,
|
|
14
|
+
kind: options.kind,
|
|
15
|
+
...(options.subject === undefined ? {} : { subject: options.subject }),
|
|
16
|
+
...(options.parent === undefined ? {} : { parent: options.parent }),
|
|
17
|
+
});
|
|
18
|
+
}
|
|
8
19
|
/**
|
|
9
20
|
* Single createAgentSession + Settings construction for all in-process children.
|
|
21
|
+
* Child SessionManager construction for identity-declared records lives here so
|
|
22
|
+
* role modules do not call createRecordSession directly.
|
|
10
23
|
*/
|
|
11
24
|
export async function openInProcessAgentSession(options) {
|
|
12
25
|
const settings = SettingsManager.inMemory({ compaction: { enabled: false }, retry: { enabled: false } });
|
|
26
|
+
const sessionManager = resolveSessionManager(options);
|
|
13
27
|
const createArgs = {
|
|
14
28
|
cwd: options.cwd,
|
|
15
29
|
model: options.model,
|
|
16
30
|
thinkingLevel: options.thinkingLevel ?? "off",
|
|
17
31
|
modelRuntime: options.modelRuntime,
|
|
18
|
-
sessionManager
|
|
32
|
+
sessionManager,
|
|
19
33
|
settingsManager: settings,
|
|
20
34
|
...(options.noTools === undefined ? {} : { noTools: options.noTools }),
|
|
21
35
|
...(options.tools === undefined ? {} : { tools: options.tools }),
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
-
import { mkdir, readFile,
|
|
2
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
3
3
|
import { homedir } from "node:os";
|
|
4
4
|
import { dirname, join, resolve } from "node:path";
|
|
5
|
-
import { ModelRuntime
|
|
5
|
+
import { ModelRuntime } from "@earendil-works/pi-coding-agent";
|
|
6
6
|
import { createAssistantMessageEventStream } from "@earendil-works/pi-ai";
|
|
7
7
|
import { Type } from "typebox";
|
|
8
8
|
import { Value } from "typebox/value";
|
|
@@ -11,8 +11,6 @@ import {
|
|
|
11
11
|
mintNavigatorInvocationId
|
|
12
12
|
} from "./navigator-invocation-identity.js";
|
|
13
13
|
import { PACKAGED_ROLE_REGISTRY, packagedRoleMetadata } from "./packaged-role-registry.js";
|
|
14
|
-
import { resolveBookKeyFromGit } from "./activation-ledger-git.js";
|
|
15
|
-
import { activationBookDirectory, resolveActivationLedgerHome } from "./activation-ledger-topology.js";
|
|
16
14
|
import { openInProcessAgentSession } from "./in-process-session.js";
|
|
17
15
|
import { renderPublicAkRoleCommand } from "./public-command-renderer.js";
|
|
18
16
|
import { issueRoot, subjectPath } from "./work-subject-identity.js";
|
|
@@ -218,14 +216,6 @@ function navigatorSubjectKeyForInput(subjectRoot, reference, cwd = process.cwd()
|
|
|
218
216
|
}
|
|
219
217
|
return navigatorSubjectKey(subjectRoot, resolvedReference);
|
|
220
218
|
}
|
|
221
|
-
function subjectDirectory(cwd, subjectKey) {
|
|
222
|
-
const book = activationBookDirectory(
|
|
223
|
-
resolveActivationLedgerHome(),
|
|
224
|
-
resolveBookKeyFromGit(cwd)
|
|
225
|
-
);
|
|
226
|
-
const digest = createHash("sha256").update(subjectKey).digest("hex").slice(0, 32);
|
|
227
|
-
return join(book, "navigator", digest);
|
|
228
|
-
}
|
|
229
219
|
function navigatorModelSettingPath() {
|
|
230
220
|
return join(process.env.PI_CODING_AGENT_DIR ?? join(homedir(), ".pi", "agent"), "navigator-model.json");
|
|
231
221
|
}
|
|
@@ -341,7 +331,6 @@ function createNavigatorAttendance(options) {
|
|
|
341
331
|
let subject = options.subject;
|
|
342
332
|
let authority = options.authority;
|
|
343
333
|
let contextError = options.contextError;
|
|
344
|
-
let sessionDir = options.sessionDir;
|
|
345
334
|
let candidates;
|
|
346
335
|
const invocationPrincipal = options.invocationId ?? mintNavigatorInvocationId();
|
|
347
336
|
let activeInvocationId = invocationPrincipal;
|
|
@@ -454,7 +443,7 @@ ${text}
|
|
|
454
443
|
sessionReady = (async () => {
|
|
455
444
|
let created;
|
|
456
445
|
try {
|
|
457
|
-
created = await options.createSession({ context: options.context,
|
|
446
|
+
created = await options.createSession({ context: options.context, subject: subjectKey, ...options.modelSettingPath === void 0 ? {} : { modelSettingPath: options.modelSettingPath }, tool });
|
|
458
447
|
} catch (error) {
|
|
459
448
|
throw navigatorUnavailableError("session", error);
|
|
460
449
|
}
|
|
@@ -584,7 +573,7 @@ ${helpContext}
|
|
|
584
573
|
await promptAllowingRejectedPrepare(RECEIPT_DELIVERY_PROMPT, true);
|
|
585
574
|
}
|
|
586
575
|
if (output === void 0 && delivery.nextAction() === "no-receipt" && activeSession.providerFailure?.() === void 0) {
|
|
587
|
-
const facts = delivery.facts({ runPointer:
|
|
576
|
+
const facts = delivery.facts({ runPointer: activeSession.recordPointer(), attemptPointer: invocationId });
|
|
588
577
|
activeSession.appendEntry(NO_RECEIPT_LIFECYCLE_ENTRY_TYPE, facts);
|
|
589
578
|
preparationNoReceipt = true;
|
|
590
579
|
candidates = [];
|
|
@@ -622,7 +611,6 @@ ${helpContext}
|
|
|
622
611
|
subject = next.subject;
|
|
623
612
|
authority = next.authority;
|
|
624
613
|
contextError = next.contextError;
|
|
625
|
-
sessionDir = options.sessionDirectory?.(next.subjectKey) ?? options.sessionDir;
|
|
626
614
|
},
|
|
627
615
|
/**
|
|
628
616
|
* Start live-help subprocesses during activation without beginning full
|
|
@@ -777,7 +765,7 @@ ${helpContext}
|
|
|
777
765
|
}
|
|
778
766
|
function createNativeNavigatorSessionFactory(defaultModelSettingPath = navigatorModelSettingPath()) {
|
|
779
767
|
const sharedModelRuntime = ModelRuntime.create({ allowModelNetwork: false });
|
|
780
|
-
return async ({ context,
|
|
768
|
+
return async ({ context, subject, modelSettingPath, tool }) => {
|
|
781
769
|
let configured;
|
|
782
770
|
try {
|
|
783
771
|
configured = await readNavigatorModelSetting(modelSettingPath ?? defaultModelSettingPath);
|
|
@@ -800,15 +788,6 @@ function createNativeNavigatorSessionFactory(defaultModelSettingPath = navigator
|
|
|
800
788
|
throw navigatorUnavailableError("auth", error);
|
|
801
789
|
}
|
|
802
790
|
if (!auth.ok) throw new NavigatorUnavailableError("auth", auth.error);
|
|
803
|
-
try {
|
|
804
|
-
const sessionInfo = await stat(sessionDir);
|
|
805
|
-
if (!sessionInfo.isDirectory()) throw new NavigatorUnavailableError("session", `Navigator session path is not a directory: ${sessionDir}`);
|
|
806
|
-
} catch (error) {
|
|
807
|
-
if (error instanceof NavigatorUnavailableError) throw error;
|
|
808
|
-
if (!(error instanceof Error && "code" in error && error.code === "ENOENT")) {
|
|
809
|
-
throw navigatorUnavailableError("session", error);
|
|
810
|
-
}
|
|
811
|
-
}
|
|
812
791
|
let providerFailure;
|
|
813
792
|
const assignProviderFailure = (fact) => {
|
|
814
793
|
if (fact !== void 0) providerFailure = fact;
|
|
@@ -959,10 +938,12 @@ function createNativeNavigatorSessionFactory(defaultModelSettingPath = navigator
|
|
|
959
938
|
try {
|
|
960
939
|
opened = await openInProcessAgentSession({
|
|
961
940
|
cwd: context.cwd,
|
|
941
|
+
kind: "navigator",
|
|
942
|
+
subject,
|
|
943
|
+
parent: context.sessionManager,
|
|
962
944
|
model,
|
|
963
945
|
modelRuntime,
|
|
964
946
|
thinkingLevel: parsed.thinkingLevel,
|
|
965
|
-
sessionManager: SessionManager.continueRecent(context.cwd, sessionDir),
|
|
966
947
|
noTools: "all",
|
|
967
948
|
tools: [NAVIGATOR_PREPARE_TOOL_NAME],
|
|
968
949
|
customTools: [tool]
|
|
@@ -1016,6 +997,7 @@ function createNativeNavigatorSessionFactory(defaultModelSettingPath = navigator
|
|
|
1016
997
|
}
|
|
1017
998
|
},
|
|
1018
999
|
getThinkingLevel: () => opened.session.thinkingLevel,
|
|
1000
|
+
recordPointer: () => opened.session.sessionManager.getSessionDir(),
|
|
1019
1001
|
dispose: () => opened.dispose()
|
|
1020
1002
|
};
|
|
1021
1003
|
};
|
|
@@ -1028,11 +1010,6 @@ function registerNavigatorModelCommand(pi, path = navigatorModelSettingPath()) {
|
|
|
1028
1010
|
}
|
|
1029
1011
|
});
|
|
1030
1012
|
}
|
|
1031
|
-
function navigatorSessionDirectory(context, subjectKey) {
|
|
1032
|
-
const current = context.sessionManager.getSessionDir();
|
|
1033
|
-
const key = subjectKey ?? subjectPath(current, context.cwd);
|
|
1034
|
-
return subjectDirectory(context.cwd, key);
|
|
1035
|
-
}
|
|
1036
1013
|
export {
|
|
1037
1014
|
NAVIGATOR_DEFAULT_MODEL,
|
|
1038
1015
|
NAVIGATOR_EVENT_TYPE,
|
|
@@ -1048,7 +1025,6 @@ export {
|
|
|
1048
1025
|
navigatorModelSettingPath,
|
|
1049
1026
|
navigatorProviderFailure,
|
|
1050
1027
|
navigatorProviderFailureFromError,
|
|
1051
|
-
navigatorSessionDirectory,
|
|
1052
1028
|
navigatorSubjectKey,
|
|
1053
1029
|
navigatorSubjectKeyForInput,
|
|
1054
1030
|
navigatorUnavailableError,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
1
2
|
import { dirname, resolve, join } from "node:path";
|
|
2
3
|
import { SessionManager } from "@earendil-works/pi-coding-agent";
|
|
3
4
|
import { resolveBookKeyFromGit } from "./activation-ledger-git.js";
|
|
@@ -10,13 +11,26 @@ import {
|
|
|
10
11
|
physicallyContainedIn,
|
|
11
12
|
resolveActivationLedgerHome
|
|
12
13
|
} from "./activation-ledger-topology.js";
|
|
14
|
+
const { findMostRecentSession } = await import(new URL("./core/session-manager.js", import.meta.resolve("@earendil-works/pi-coding-agent")).href);
|
|
13
15
|
function createRecordSession(options) {
|
|
14
16
|
const cwd = options.cwd;
|
|
15
17
|
const parentFile = options.parent?.getSessionFile();
|
|
18
|
+
const ledgerHome = resolveActivationLedgerHome();
|
|
19
|
+
if (options.subject !== void 0) {
|
|
20
|
+
const digest = createHash("sha256").update(options.subject).digest("hex").slice(0, 32);
|
|
21
|
+
const sessionDir2 = join(
|
|
22
|
+
activationBookDirectory(ledgerHome, resolveBookKeyFromGit(cwd)),
|
|
23
|
+
options.kind,
|
|
24
|
+
digest
|
|
25
|
+
);
|
|
26
|
+
ensureRealDirectoryTree(ledgerHome, sessionDir2);
|
|
27
|
+
const recentFile = findMostRecentSession(sessionDir2, cwd);
|
|
28
|
+
if (recentFile !== null) return SessionManager.open(recentFile, sessionDir2, cwd);
|
|
29
|
+
return SessionManager.create(cwd, sessionDir2, parentFile ? { parentSession: parentFile } : void 0);
|
|
30
|
+
}
|
|
16
31
|
if (parentFile === void 0 || parentFile.length === 0) {
|
|
17
32
|
return SessionManager.inMemory(cwd);
|
|
18
33
|
}
|
|
19
|
-
const ledgerHome = resolveActivationLedgerHome();
|
|
20
34
|
const parentResolved = resolve(parentFile);
|
|
21
35
|
const sessionDir = physicallyContainedIn(ledgerHome, parentResolved) ? join(dirname(parentResolved), options.kind) : join(activationBookDirectory(ledgerHome, resolveBookKeyFromGit(cwd)), options.kind);
|
|
22
36
|
ensureRealDirectoryTree(ledgerHome, sessionDir);
|
|
@@ -23,7 +23,6 @@ import {
|
|
|
23
23
|
createNativeNavigatorSessionFactory,
|
|
24
24
|
createNavigatorAttendance,
|
|
25
25
|
navigatorUnavailableError,
|
|
26
|
-
navigatorSessionDirectory,
|
|
27
26
|
navigatorSubjectKey,
|
|
28
27
|
navigatorSubjectKeyForInput,
|
|
29
28
|
registerNavigatorModelCommand,
|
|
@@ -261,14 +260,11 @@ export default function roleRuntime(pi: ExtensionAPI): void {
|
|
|
261
260
|
auditDoctorCompliance: createPiDoctorAuditor(),
|
|
262
261
|
loadNavigatorWorkContext: (options) => loadNavigatorWorkContext(pi, options),
|
|
263
262
|
createNavigatorAttendance: (options) => {
|
|
264
|
-
const sessionDir = navigatorSessionDirectory(options.context, options.subjectKey);
|
|
265
263
|
return createNavigatorAttendance({
|
|
266
264
|
context: options.context,
|
|
267
265
|
role: options.role,
|
|
268
266
|
phase: options.phase,
|
|
269
267
|
subjectKey: options.subjectKey,
|
|
270
|
-
sessionDir,
|
|
271
|
-
sessionDirectory: (subjectKey) => navigatorSessionDirectory(options.context, subjectKey),
|
|
272
268
|
subject: options.subject,
|
|
273
269
|
authority: options.authority,
|
|
274
270
|
invocationId: options.invocationId,
|
package/package.json
CHANGED
|
@@ -14,14 +14,15 @@ import {
|
|
|
14
14
|
} from "@earendil-works/pi-coding-agent";
|
|
15
15
|
import type { Api, Model } from "@earendil-works/pi-ai";
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
import { createRecordSession } from "./sitian-record-entry.ts";
|
|
18
|
+
|
|
19
|
+
type OpenInProcessAgentSessionBase = {
|
|
18
20
|
readonly cwd: string;
|
|
19
21
|
/** Required when loading a resource loader / systemPrompt. Callers own scratch lifecycle. */
|
|
20
22
|
readonly agentDir?: string;
|
|
21
23
|
readonly model: Model<Api>;
|
|
22
24
|
readonly modelRuntime: ModelRuntime;
|
|
23
25
|
readonly thinkingLevel?: "off" | "minimal" | "low" | "medium" | "high" | "xhigh" | "max";
|
|
24
|
-
readonly sessionManager: SessionManager;
|
|
25
26
|
readonly systemPrompt?: string;
|
|
26
27
|
readonly customTools?: ToolDefinition[];
|
|
27
28
|
readonly noTools?: "all" | "builtin";
|
|
@@ -29,20 +30,56 @@ export type OpenInProcessAgentSessionOptions = {
|
|
|
29
30
|
readonly tools?: string[];
|
|
30
31
|
};
|
|
31
32
|
|
|
33
|
+
/**
|
|
34
|
+
* Shared open admits exactly one session source:
|
|
35
|
+
* - pre-built sessionManager, or
|
|
36
|
+
* - kind/subject/parent identity relations (Sitian obtains the manager).
|
|
37
|
+
* Encoded as a type relation — no runtime missing-arg guard.
|
|
38
|
+
*/
|
|
39
|
+
export type OpenInProcessAgentSessionOptions =
|
|
40
|
+
| (OpenInProcessAgentSessionBase & {
|
|
41
|
+
readonly sessionManager: SessionManager;
|
|
42
|
+
readonly kind?: never;
|
|
43
|
+
readonly subject?: never;
|
|
44
|
+
readonly parent?: never;
|
|
45
|
+
})
|
|
46
|
+
| (OpenInProcessAgentSessionBase & {
|
|
47
|
+
readonly sessionManager?: undefined;
|
|
48
|
+
/** Record kind for the Sitian entry. */
|
|
49
|
+
readonly kind: string;
|
|
50
|
+
/** Stable work identity for book-level records which continue across role runs. */
|
|
51
|
+
readonly subject?: string;
|
|
52
|
+
/** Optional parent session — supplies parentSession link / nest principal. */
|
|
53
|
+
readonly parent?: { getSessionFile(): string | undefined };
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
function resolveSessionManager(options: OpenInProcessAgentSessionOptions): SessionManager {
|
|
57
|
+
if (options.sessionManager !== undefined) return options.sessionManager;
|
|
58
|
+
return createRecordSession({
|
|
59
|
+
cwd: options.cwd,
|
|
60
|
+
kind: options.kind,
|
|
61
|
+
...(options.subject === undefined ? {} : { subject: options.subject }),
|
|
62
|
+
...(options.parent === undefined ? {} : { parent: options.parent }),
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
32
66
|
/**
|
|
33
67
|
* Single createAgentSession + Settings construction for all in-process children.
|
|
68
|
+
* Child SessionManager construction for identity-declared records lives here so
|
|
69
|
+
* role modules do not call createRecordSession directly.
|
|
34
70
|
*/
|
|
35
71
|
export async function openInProcessAgentSession(
|
|
36
72
|
options: OpenInProcessAgentSessionOptions,
|
|
37
73
|
): Promise<{ session: Awaited<ReturnType<typeof createAgentSession>>["session"]; dispose(): void }> {
|
|
38
74
|
const settings = SettingsManager.inMemory({ compaction: { enabled: false }, retry: { enabled: false } });
|
|
75
|
+
const sessionManager = resolveSessionManager(options);
|
|
39
76
|
|
|
40
77
|
const createArgs: Parameters<typeof createAgentSession>[0] = {
|
|
41
78
|
cwd: options.cwd,
|
|
42
79
|
model: options.model,
|
|
43
80
|
thinkingLevel: options.thinkingLevel ?? "off",
|
|
44
81
|
modelRuntime: options.modelRuntime,
|
|
45
|
-
sessionManager
|
|
82
|
+
sessionManager,
|
|
46
83
|
settingsManager: settings,
|
|
47
84
|
...(options.noTools === undefined ? {} : { noTools: options.noTools }),
|
|
48
85
|
...(options.tools === undefined ? {} : { tools: options.tools }),
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
-
import { mkdir, readFile,
|
|
2
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
3
3
|
import { homedir } from "node:os";
|
|
4
4
|
import { dirname, join, resolve } from "node:path";
|
|
5
5
|
|
|
6
|
-
import { ModelRuntime,
|
|
6
|
+
import { ModelRuntime, type ExtensionContext, type ExtensionAPI, type ToolDefinition } from "@earendil-works/pi-coding-agent";
|
|
7
7
|
import { createAssistantMessageEventStream } from "@earendil-works/pi-ai";
|
|
8
8
|
import { Type, type Static } from "typebox";
|
|
9
9
|
import { Value } from "typebox/value";
|
|
@@ -13,8 +13,6 @@ import {
|
|
|
13
13
|
mintNavigatorInvocationId,
|
|
14
14
|
} from "./navigator-invocation-identity.ts";
|
|
15
15
|
import { PACKAGED_ROLE_REGISTRY, type PackagedRole, packagedRoleMetadata } from "./packaged-role-registry.ts";
|
|
16
|
-
import { resolveBookKeyFromGit } from "./activation-ledger-git.ts";
|
|
17
|
-
import { activationBookDirectory, resolveActivationLedgerHome } from "./activation-ledger-topology.ts";
|
|
18
16
|
import { openInProcessAgentSession } from "./in-process-session.ts";
|
|
19
17
|
import { renderPublicAkRoleCommand } from "./public-command-renderer.ts";
|
|
20
18
|
import { issueRoot, subjectPath } from "./work-subject-identity.ts";
|
|
@@ -233,12 +231,14 @@ export type NavigatorPreparationSession = {
|
|
|
233
231
|
providerFailure?(): NavigatorProviderFailureFact | undefined;
|
|
234
232
|
setModel?(model: string, thinkingLevel: "off" | "max"): Promise<void>;
|
|
235
233
|
getThinkingLevel?(): string;
|
|
234
|
+
/** Durable record pointer for unchanged no-receipt lifecycle facts. Required — missing pointer fails honestly. */
|
|
235
|
+
recordPointer(): string;
|
|
236
236
|
dispose(): void;
|
|
237
237
|
};
|
|
238
238
|
|
|
239
239
|
export type NavigatorSessionFactory = (options: {
|
|
240
240
|
context: ExtensionContext;
|
|
241
|
-
|
|
241
|
+
subject: string;
|
|
242
242
|
modelSettingPath?: string;
|
|
243
243
|
tool: ToolDefinition;
|
|
244
244
|
}) => Promise<NavigatorPreparationSession>;
|
|
@@ -248,7 +248,6 @@ export type NavigatorAttendanceOptions = {
|
|
|
248
248
|
role: string;
|
|
249
249
|
phase: NavigatorPhase;
|
|
250
250
|
subjectKey: string;
|
|
251
|
-
sessionDir: string;
|
|
252
251
|
loadSoul: () => Promise<string>;
|
|
253
252
|
loadRoutePlaybook?: () => Promise<string>;
|
|
254
253
|
loadRoleHelp: (role: NavigatorTargetRole) => Promise<string>;
|
|
@@ -257,7 +256,6 @@ export type NavigatorAttendanceOptions = {
|
|
|
257
256
|
subject: string;
|
|
258
257
|
authority: string;
|
|
259
258
|
contextError?: unknown;
|
|
260
|
-
sessionDirectory?: (subjectKey: string) => string;
|
|
261
259
|
/** Exact principal owned by shared role lifecycle; attendance never overrides it. */
|
|
262
260
|
invocationId?: string;
|
|
263
261
|
onEvent: (event: NavigatorEvent, report: NavigatorReport) => void | Promise<void>;
|
|
@@ -426,15 +424,6 @@ export function navigatorSubjectKeyForInput(subjectRoot: string, reference: stri
|
|
|
426
424
|
return navigatorSubjectKey(subjectRoot, resolvedReference);
|
|
427
425
|
}
|
|
428
426
|
|
|
429
|
-
function subjectDirectory(cwd: string, subjectKey: string): string {
|
|
430
|
-
const book = activationBookDirectory(
|
|
431
|
-
resolveActivationLedgerHome(),
|
|
432
|
-
resolveBookKeyFromGit(cwd),
|
|
433
|
-
);
|
|
434
|
-
const digest = createHash("sha256").update(subjectKey).digest("hex").slice(0, 32);
|
|
435
|
-
return join(book, "navigator", digest);
|
|
436
|
-
}
|
|
437
|
-
|
|
438
427
|
export function navigatorModelSettingPath(): string {
|
|
439
428
|
return join(process.env.PI_CODING_AGENT_DIR ?? join(homedir(), ".pi", "agent"), "navigator-model.json");
|
|
440
429
|
}
|
|
@@ -588,7 +577,6 @@ export function createNavigatorAttendance(options: NavigatorAttendanceOptions) {
|
|
|
588
577
|
let subject = options.subject;
|
|
589
578
|
let authority = options.authority;
|
|
590
579
|
let contextError = options.contextError;
|
|
591
|
-
let sessionDir = options.sessionDir;
|
|
592
580
|
let candidates: NavigatorCandidate[] | undefined;
|
|
593
581
|
// Shared lifecycle owns the principal when supplied; otherwise mint once per attendance.
|
|
594
582
|
const invocationPrincipal = options.invocationId ?? mintNavigatorInvocationId();
|
|
@@ -721,7 +709,7 @@ export function createNavigatorAttendance(options: NavigatorAttendanceOptions) {
|
|
|
721
709
|
sessionReady = (async () => {
|
|
722
710
|
let created: NavigatorPreparationSession;
|
|
723
711
|
try {
|
|
724
|
-
created = await options.createSession({ context: options.context,
|
|
712
|
+
created = await options.createSession({ context: options.context, subject: subjectKey, ...(options.modelSettingPath === undefined ? {} : { modelSettingPath: options.modelSettingPath }), tool });
|
|
725
713
|
} catch (error) {
|
|
726
714
|
throw navigatorUnavailableError("session", error);
|
|
727
715
|
}
|
|
@@ -841,7 +829,7 @@ export function createNavigatorAttendance(options: NavigatorAttendanceOptions) {
|
|
|
841
829
|
await promptAllowingRejectedPrepare(RECEIPT_DELIVERY_PROMPT, true);
|
|
842
830
|
}
|
|
843
831
|
if (output === undefined && delivery.nextAction() === "no-receipt" && activeSession.providerFailure?.() === undefined) {
|
|
844
|
-
const facts = delivery.facts({ runPointer:
|
|
832
|
+
const facts = delivery.facts({ runPointer: activeSession.recordPointer(), attemptPointer: invocationId });
|
|
845
833
|
activeSession.appendEntry(NO_RECEIPT_LIFECYCLE_ENTRY_TYPE, facts);
|
|
846
834
|
preparationNoReceipt = true;
|
|
847
835
|
candidates = [];
|
|
@@ -884,7 +872,6 @@ export function createNavigatorAttendance(options: NavigatorAttendanceOptions) {
|
|
|
884
872
|
subject = next.subject;
|
|
885
873
|
authority = next.authority;
|
|
886
874
|
contextError = next.contextError;
|
|
887
|
-
sessionDir = options.sessionDirectory?.(next.subjectKey) ?? options.sessionDir;
|
|
888
875
|
},
|
|
889
876
|
/**
|
|
890
877
|
* Start live-help subprocesses during activation without beginning full
|
|
@@ -1056,7 +1043,7 @@ export function createNativeNavigatorSessionFactory(defaultModelSettingPath = na
|
|
|
1056
1043
|
// Pre-warm once per factory (per process). Concurrent prepares share the same
|
|
1057
1044
|
// runtime construction rather than each paying ModelRuntime.create under load.
|
|
1058
1045
|
const sharedModelRuntime = ModelRuntime.create({ allowModelNetwork: false });
|
|
1059
|
-
return async ({ context,
|
|
1046
|
+
return async ({ context, subject, modelSettingPath, tool }) => {
|
|
1060
1047
|
let configured: string;
|
|
1061
1048
|
try {
|
|
1062
1049
|
configured = await readNavigatorModelSetting(modelSettingPath ?? defaultModelSettingPath);
|
|
@@ -1079,16 +1066,6 @@ export function createNativeNavigatorSessionFactory(defaultModelSettingPath = na
|
|
|
1079
1066
|
throw navigatorUnavailableError("auth", error);
|
|
1080
1067
|
}
|
|
1081
1068
|
if (!auth.ok) throw new NavigatorUnavailableError("auth", auth.error);
|
|
1082
|
-
try {
|
|
1083
|
-
const sessionInfo = await stat(sessionDir);
|
|
1084
|
-
if (!sessionInfo.isDirectory()) throw new NavigatorUnavailableError("session", `Navigator session path is not a directory: ${sessionDir}`);
|
|
1085
|
-
} catch (error) {
|
|
1086
|
-
if (error instanceof NavigatorUnavailableError) throw error;
|
|
1087
|
-
// Contract: README.md#Navigator-attendance — the resumable session directory may be created by session setup; retain and classify every other filesystem cause.
|
|
1088
|
-
if (!(error instanceof Error && "code" in error && (error as NodeJS.ErrnoException).code === "ENOENT")) {
|
|
1089
|
-
throw navigatorUnavailableError("session", error);
|
|
1090
|
-
}
|
|
1091
|
-
}
|
|
1092
1069
|
let providerFailure: NavigatorProviderFailureFact | undefined;
|
|
1093
1070
|
const assignProviderFailure = (fact: NavigatorProviderFailureFact | undefined): void => {
|
|
1094
1071
|
if (fact !== undefined) providerFailure = fact;
|
|
@@ -1253,13 +1230,15 @@ export function createNativeNavigatorSessionFactory(defaultModelSettingPath = na
|
|
|
1253
1230
|
}
|
|
1254
1231
|
let opened: Awaited<ReturnType<typeof openInProcessAgentSession>>;
|
|
1255
1232
|
try {
|
|
1256
|
-
// Shared in-process session open (#233) —
|
|
1233
|
+
// Shared in-process session open (#233) — Sitian SessionManager from identity relations.
|
|
1257
1234
|
opened = await openInProcessAgentSession({
|
|
1258
1235
|
cwd: context.cwd,
|
|
1236
|
+
kind: "navigator",
|
|
1237
|
+
subject,
|
|
1238
|
+
parent: context.sessionManager,
|
|
1259
1239
|
model,
|
|
1260
1240
|
modelRuntime,
|
|
1261
1241
|
thinkingLevel: parsed.thinkingLevel,
|
|
1262
|
-
sessionManager: SessionManager.continueRecent(context.cwd, sessionDir),
|
|
1263
1242
|
noTools: "all",
|
|
1264
1243
|
tools: [NAVIGATOR_PREPARE_TOOL_NAME],
|
|
1265
1244
|
customTools: [tool],
|
|
@@ -1312,6 +1291,7 @@ export function createNativeNavigatorSessionFactory(defaultModelSettingPath = na
|
|
|
1312
1291
|
}
|
|
1313
1292
|
},
|
|
1314
1293
|
getThinkingLevel: () => opened.session.thinkingLevel,
|
|
1294
|
+
recordPointer: () => opened.session.sessionManager.getSessionDir(),
|
|
1315
1295
|
dispose: () => opened.dispose(),
|
|
1316
1296
|
};
|
|
1317
1297
|
};
|
|
@@ -1326,10 +1306,4 @@ export function registerNavigatorModelCommand(pi: ExtensionAPI, path = navigator
|
|
|
1326
1306
|
});
|
|
1327
1307
|
}
|
|
1328
1308
|
|
|
1329
|
-
export function navigatorSessionDirectory(context: ExtensionContext, subjectKey?: string): string {
|
|
1330
|
-
const current = context.sessionManager.getSessionDir();
|
|
1331
|
-
const key = subjectKey ?? subjectPath(current, context.cwd);
|
|
1332
|
-
return subjectDirectory(context.cwd, key);
|
|
1333
|
-
}
|
|
1334
|
-
|
|
1335
1309
|
export { subjectPath };
|
package/src/reviewer-role.ts
CHANGED
|
@@ -120,7 +120,7 @@ export function createReviewerRoleRuntime(pi: ExtensionAPI, dependencies: Review
|
|
|
120
120
|
if (!registered) {
|
|
121
121
|
registered = true;
|
|
122
122
|
pi.registerTool({ name: REVIEWER_OUTPUT_TOOL_NAME, label: "Reviewer Output", description: "Submit the thin Reviewer receipt after semantic compliance audit.", promptSnippet: "Submit the final Reviewer receipt", promptGuidelines: [`Use ${REVIEWER_OUTPUT_TOOL_NAME} as the sole final action.`,
|
|
123
|
-
"This runtime
|
|
123
|
+
"This runtime executes the Standards and Spec review legs for you as package-managed evidence-child sessions — that IS this runtime's implementation of the review Skill's parallel sub-agents. Do not refuse because no Agent tool appears in your tool list, and do not substitute your own sub-processes; work with the legs the runtime provides."], parameters: reviewerOutputSchema,
|
|
124
124
|
async execute(id, parameters, signal, _update, toolCtx): Promise<AgentToolResult<unknown>> {
|
|
125
125
|
if (!soul || !binding) throw new Error("Reviewer inputs were not loaded");
|
|
126
126
|
requireSoleReviewerOutputCall(id, toolCtx);
|
package/src/role-runtime.ts
CHANGED
|
@@ -337,7 +337,7 @@ export type RoleRuntimeDependencies = {
|
|
|
337
337
|
auditDoctorCompliance?(options: { context: ExtensionContext; signal?: AbortSignal }): Promise<ComplianceDecision>;
|
|
338
338
|
createCollectorClock?(): CollectorClock;
|
|
339
339
|
collectorPackageExtensionPath?: string;
|
|
340
|
-
createNavigatorAttendance?(options: { context: ExtensionContext; role: string; phase: NavigatorPhase; subjectKey: string; subject: string; authority: string; contextError?: unknown;
|
|
340
|
+
createNavigatorAttendance?(options: { context: ExtensionContext; role: string; phase: NavigatorPhase; subjectKey: string; subject: string; authority: string; contextError?: unknown; invocationId: string; onEvent: (event: import("./navigator-attendance.ts").NavigatorEvent, report: import("./navigator-attendance.ts").NavigatorReport) => void | Promise<void> }): NavigatorAttendanceDependency | Promise<NavigatorAttendanceDependency>;
|
|
341
341
|
loadNavigatorWorkContext?(options: { context: ExtensionContext; role: string; phase: NavigatorPhase }): Promise<NavigatorWorkContext>;
|
|
342
342
|
loadCanonicalSkillBinding?(
|
|
343
343
|
name: "tdd" | "code-review",
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* 调用方只声明自己是谁的什么;落点由候簿拓扑算出,签名不含任何落点/路径参数。
|
|
4
4
|
* 「谁调了谁」复用 Pi parentSession + ADR 0047 correlation,不新增 caller 字段。
|
|
5
5
|
*/
|
|
6
|
+
import { createHash } from "node:crypto";
|
|
6
7
|
import { dirname, resolve, join } from "node:path";
|
|
7
8
|
import { SessionManager } from "@earendil-works/pi-coding-agent";
|
|
8
9
|
|
|
@@ -18,6 +19,13 @@ import {
|
|
|
18
19
|
resolveActivationLedgerHome,
|
|
19
20
|
} from "./activation-ledger-topology.ts";
|
|
20
21
|
|
|
22
|
+
// continueRecent always allocates a deferred sessionFile; this is the discovery result.
|
|
23
|
+
const { findMostRecentSession } = await import(
|
|
24
|
+
new URL("./core/session-manager.js", import.meta.resolve("@earendil-works/pi-coding-agent")).href,
|
|
25
|
+
) as {
|
|
26
|
+
findMostRecentSession: (sessionDir: string, cwd?: string) => string | null;
|
|
27
|
+
};
|
|
28
|
+
|
|
21
29
|
/** Parent session surface needed to link and (when already under home) nest the record. */
|
|
22
30
|
export type RecordSessionParent = {
|
|
23
31
|
getSessionFile(): string | undefined;
|
|
@@ -30,6 +38,8 @@ export type CreateRecordSessionOptions = {
|
|
|
30
38
|
readonly kind: string;
|
|
31
39
|
/** Optional parent session — supplies parentSession link; nest under parent only when that parent already lives under the ledger home. */
|
|
32
40
|
readonly parent?: RecordSessionParent;
|
|
41
|
+
/** Stable work identity for book-level records which continue across role runs. */
|
|
42
|
+
readonly subject?: string;
|
|
33
43
|
};
|
|
34
44
|
|
|
35
45
|
/**
|
|
@@ -39,12 +49,30 @@ export type CreateRecordSessionOptions = {
|
|
|
39
49
|
export function createRecordSession(options: CreateRecordSessionOptions): SessionManager {
|
|
40
50
|
const cwd = options.cwd;
|
|
41
51
|
const parentFile = options.parent?.getSessionFile();
|
|
52
|
+
const ledgerHome = resolveActivationLedgerHome();
|
|
53
|
+
|
|
54
|
+
if (options.subject !== undefined) {
|
|
55
|
+
const digest = createHash("sha256").update(options.subject).digest("hex").slice(0, 32);
|
|
56
|
+
const sessionDir = join(
|
|
57
|
+
activationBookDirectory(ledgerHome, resolveBookKeyFromGit(cwd)),
|
|
58
|
+
options.kind,
|
|
59
|
+
digest,
|
|
60
|
+
);
|
|
61
|
+
ensureRealDirectoryTree(ledgerHome, sessionDir);
|
|
62
|
+
// Delegate discovery to Pi so custom-directory continuation retains its
|
|
63
|
+
// mtime ordering, valid-header scan, and cwd filtering semantics.
|
|
64
|
+
const recentFile = findMostRecentSession(sessionDir, cwd);
|
|
65
|
+
if (recentFile !== null) return SessionManager.open(recentFile, sessionDir, cwd);
|
|
66
|
+
return SessionManager.create(cwd, sessionDir, parentFile
|
|
67
|
+
? { parentSession: parentFile }
|
|
68
|
+
: undefined);
|
|
69
|
+
}
|
|
70
|
+
|
|
42
71
|
if (parentFile === undefined || parentFile.length === 0) {
|
|
43
72
|
// No durable parent principal — preserve prior in-memory child behavior.
|
|
44
73
|
return SessionManager.inMemory(cwd);
|
|
45
74
|
}
|
|
46
75
|
|
|
47
|
-
const ledgerHome = resolveActivationLedgerHome();
|
|
48
76
|
const parentResolved = resolve(parentFile);
|
|
49
77
|
|
|
50
78
|
// Nest under parent only when the parent record already lives under the package home.
|