@akagilnc/pi-workflow-roles 0.1.3826 → 0.1.3837

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.
@@ -16584,7 +16584,7 @@ var init_sitian_facade = __esm({
16584
16584
  import { execFile, spawn } from "node:child_process";
16585
16585
  import { constants } from "node:fs";
16586
16586
  import { access, appendFile, readFile as readFile3, realpath } from "node:fs/promises";
16587
- import { delimiter as delimiter2, isAbsolute as isAbsolute3, join as join10, resolve as resolve4 } from "node:path";
16587
+ import { basename as basename4, delimiter as delimiter2, dirname as dirname6, isAbsolute as isAbsolute3, join as join10, resolve as resolve4 } from "node:path";
16588
16588
  import { platform } from "node:process";
16589
16589
  import { promisify } from "node:util";
16590
16590
  import { randomUUID as randomUUID2 } from "node:crypto";
@@ -16692,12 +16692,26 @@ function buildMethodArgs(methods) {
16692
16692
  }
16693
16693
  return skillArgs;
16694
16694
  }
16695
+ function applyPiNativeSkillInvocation(methods, prompt) {
16696
+ const skills = methods.filter((method) => method.kind === "skill");
16697
+ if (skills.length !== 1) return prompt;
16698
+ const name = basename4(dirname6(skills[0].path));
16699
+ if (name.length === 0) return prompt;
16700
+ const token = `/skill:${name}`;
16701
+ const trimmed = prompt.trimStart();
16702
+ if (trimmed === token || trimmed.startsWith(`${token} `) || trimmed.startsWith(`${token}
16703
+ `)) {
16704
+ return prompt;
16705
+ }
16706
+ return prompt.length === 0 ? token : `${token} ${prompt}`;
16707
+ }
16695
16708
  function buildPiTurnExtraArgs(request, authority, extraPiArgs = []) {
16696
16709
  const { sessionFile, sessionDirectory } = authority.decode(request.principal);
16697
- const prompt = request.continuation.kind === "initial" || request.continuation.kind === "resume" ? request.continuation.prompt : (() => {
16710
+ const rawPrompt = request.continuation.kind === "initial" || request.continuation.kind === "resume" ? request.continuation.prompt : (() => {
16698
16711
  const _exhaustive = request.continuation;
16699
16712
  return _exhaustive;
16700
16713
  })();
16714
+ const prompt = applyPiNativeSkillInvocation(request.methods, rawPrompt);
16701
16715
  return [
16702
16716
  "--no-skills",
16703
16717
  ...buildMethodArgs(request.methods),
@@ -17173,7 +17187,7 @@ var init_terminating_tools = __esm({
17173
17187
 
17174
17188
  // src/doctor-evidence.ts
17175
17189
  import { readdir, readFile as readFile5, realpath as realpath2, stat } from "node:fs/promises";
17176
- import { dirname as dirname6, relative as relative2, resolve as resolve5, sep as sep2 } from "node:path";
17190
+ import { dirname as dirname7, relative as relative2, resolve as resolve5, sep as sep2 } from "node:path";
17177
17191
  function record2(value) {
17178
17192
  return typeof value === "object" && value !== null && !Array.isArray(value);
17179
17193
  }
@@ -17212,7 +17226,7 @@ async function stableRunsIdentity(root) {
17212
17226
  } catch (error) {
17213
17227
  if (!isMissingPathError(error)) throw error;
17214
17228
  }
17215
- const parent = dirname6(cursor);
17229
+ const parent = dirname7(cursor);
17216
17230
  if (parent === cursor) return root;
17217
17231
  cursor = parent;
17218
17232
  }
@@ -19025,7 +19039,7 @@ __export(notary_source_run_exports, {
19025
19039
  loadNotarySourceRunLocator: () => loadNotarySourceRunLocator,
19026
19040
  resolveNotarySourceRunLocator: () => resolveNotarySourceRunLocator
19027
19041
  });
19028
- import { dirname as dirname7, isAbsolute as isAbsolute5, join as join14, resolve as resolve6, basename as basename4 } from "node:path";
19042
+ import { dirname as dirname8, isAbsolute as isAbsolute5, join as join14, resolve as resolve6, basename as basename5 } from "node:path";
19029
19043
  import { lstat as lstat2, realpath as realpath3 } from "node:fs/promises";
19030
19044
  function parseRunDirectoryName(name) {
19031
19045
  const match = RUN_DIR_NAME.exec(name);
@@ -19056,10 +19070,10 @@ async function requireRunDirectory(candidate, display) {
19056
19070
  `notary --source-run must be a run directory: ${display}`
19057
19071
  );
19058
19072
  }
19059
- const identity = parseRunDirectoryName(basename4(real));
19073
+ const identity = parseRunDirectoryName(basename5(real));
19060
19074
  if (identity === void 0) {
19061
19075
  throw new NotarySourceRunError(
19062
- `notary --source-run must be named <runId>@<role>: ${basename4(real)}`
19076
+ `notary --source-run must be named <runId>@<role>: ${basename5(real)}`
19063
19077
  );
19064
19078
  }
19065
19079
  return real;
@@ -19080,9 +19094,9 @@ async function resolveNotarySourceRunLocator(options) {
19080
19094
  candidate = isAbsolute5(raw) ? raw : resolve6(options.projectRoot, raw);
19081
19095
  }
19082
19096
  const real = await requireRunDirectory(candidate, raw);
19083
- const identity = parseRunDirectoryName(basename4(real));
19097
+ const identity = parseRunDirectoryName(basename5(real));
19084
19098
  const runsRootIdentity = physicalPathIdentity(bookRunsRoot);
19085
- const parentIdentity = physicalPathIdentity(dirname7(real));
19099
+ const parentIdentity = physicalPathIdentity(dirname8(real));
19086
19100
  if (parentIdentity !== runsRootIdentity) {
19087
19101
  throw new NotarySourceRunError(
19088
19102
  "notary --source-run must resolve to a retained run under the project machine-ledger book"
@@ -19117,7 +19131,7 @@ async function resolveNotarySourceRunLocator(options) {
19117
19131
  }
19118
19132
  async function loadNotarySourceRunLocator(path) {
19119
19133
  const real = await requireRunDirectory(path, path);
19120
- const identity = parseRunDirectoryName(basename4(real));
19134
+ const identity = parseRunDirectoryName(basename5(real));
19121
19135
  const runState = await readRoleRunIdentity(real);
19122
19136
  if (runState === void 0) {
19123
19137
  throw new NotarySourceRunError(
@@ -20215,7 +20229,7 @@ import {
20215
20229
  realpath as realpath4,
20216
20230
  writeFile as writeFile4
20217
20231
  } from "node:fs/promises";
20218
- import { basename as basename5, isAbsolute as isAbsolute6, join as join15, resolve as resolve7, sep as sep3 } from "node:path";
20232
+ import { basename as basename6, isAbsolute as isAbsolute6, join as join15, resolve as resolve7, sep as sep3 } from "node:path";
20219
20233
  function issueAdmissionPlacement(authority, request) {
20220
20234
  const principal = authority.issue(request);
20221
20235
  const { sessionDirectory, sessionFile } = authority.decode(principal);
@@ -20609,7 +20623,7 @@ async function freezeRegularFileAttachment(sourcePath, destinationDir, index) {
20609
20623
  );
20610
20624
  }
20611
20625
  const bytes = await readFile9(absolute);
20612
- const name = `${String(index).padStart(2, "0")}-${basename5(absolute)}`;
20626
+ const name = `${String(index).padStart(2, "0")}-${basename6(absolute)}`;
20613
20627
  const frozenPath = join15(destinationDir, name);
20614
20628
  await writeFile4(frozenPath, bytes);
20615
20629
  return {
@@ -22044,9 +22058,7 @@ async function admitMergerInvocation(options) {
22044
22058
  };
22045
22059
  }
22046
22060
  function buildMergerTransportPrompt(admitted, engineMaterial) {
22047
- const lines = [
22048
- `/skill:resolving-merge-conflicts ${admitted.instruction}`
22049
- ];
22061
+ const lines = [admitted.instruction];
22050
22062
  if (admitted.attachments.length > 0) {
22051
22063
  lines.push("");
22052
22064
  lines.push("\u5DF2\u53D7\u7406\u9644\u4EF6\uFF08\u51BB\u7ED3\u5FEB\u7167\u8DEF\u5F84\uFF09\uFF1A");
@@ -23054,7 +23066,7 @@ var init_analyst_gate_cycles_read = __esm({
23054
23066
 
23055
23067
  // src/run-terminal-artifacts.ts
23056
23068
  import { readdir as readdir4, readFile as readFile12 } from "node:fs/promises";
23057
- import { basename as basename6, dirname as dirname8, join as join18 } from "node:path";
23069
+ import { basename as basename7, dirname as dirname9, join as join18 } from "node:path";
23058
23070
  function isMissingPathError2(error) {
23059
23071
  return error instanceof Error && "code" in error && (error.code === "ENOENT" || error.code === "ENOTDIR");
23060
23072
  }
@@ -23135,7 +23147,7 @@ async function listUniqueErrorFallbackPaths(directories) {
23135
23147
  return found;
23136
23148
  }
23137
23149
  function runIdFromRunDirectory(runDirectory) {
23138
- const name = basename6(runDirectory);
23150
+ const name = basename7(runDirectory);
23139
23151
  const at = name.lastIndexOf("@");
23140
23152
  if (at <= 0 || at === name.length - 1) return void 0;
23141
23153
  return name.slice(0, at);
@@ -23161,7 +23173,7 @@ async function readRunTerminalArtifact(runDirectory) {
23161
23173
  if (read3 !== void 0) return read3;
23162
23174
  }
23163
23175
  const expectedRunId = runIdFromRunDirectory(runDirectory);
23164
- for (const path of await listUniqueErrorFallbackPaths([dirname8(runDirectory)])) {
23176
+ for (const path of await listUniqueErrorFallbackPaths([dirname9(runDirectory)])) {
23165
23177
  const read3 = await readTerminalArtifactAtPath(path, "error.json");
23166
23178
  if (read3 === void 0) continue;
23167
23179
  if (read3.status === "present") {
@@ -23292,15 +23304,15 @@ var init_submission_ledger = __esm({
23292
23304
  // src/session-opening-materials.ts
23293
23305
  import { existsSync as existsSync7 } from "node:fs";
23294
23306
  import { readFile as readFile13 } from "node:fs/promises";
23295
- import { dirname as dirname9, join as join19 } from "node:path";
23307
+ import { dirname as dirname10, join as join19 } from "node:path";
23296
23308
  import { fileURLToPath, pathToFileURL as pathToFileURL3 } from "node:url";
23297
23309
  function resolvePackageRootDir(moduleUrl = import.meta.url) {
23298
- let dir = dirname9(fileURLToPath(moduleUrl));
23310
+ let dir = dirname10(fileURLToPath(moduleUrl));
23299
23311
  for (let i = 0; i < 8; i += 1) {
23300
23312
  if (existsSync7(join19(dir, "package.json")) && existsSync7(join19(dir, "souls"))) {
23301
23313
  return dir;
23302
23314
  }
23303
- const parent = dirname9(dir);
23315
+ const parent = dirname10(dir);
23304
23316
  if (parent === dir) break;
23305
23317
  dir = parent;
23306
23318
  }
@@ -23964,7 +23976,7 @@ var init_terminal = __esm({
23964
23976
  // src/public-cli/settlement.ts
23965
23977
  import { randomUUID as randomUUID3 } from "node:crypto";
23966
23978
  import { appendFile as appendFile2, readFile as readFile15, readdir as readdir5, writeFile as writeFile5 } from "node:fs/promises";
23967
- import { dirname as dirname10, join as join21 } from "node:path";
23979
+ import { dirname as dirname11, join as join21 } from "node:path";
23968
23980
  function sealedLedgerHome(admitted) {
23969
23981
  return homeFromRunDirectory(admitted.runDirectory);
23970
23982
  }
@@ -24358,7 +24370,7 @@ async function readSitianRetainedAuditorProviderStop(sessionFile) {
24358
24370
  kind: "auditor",
24359
24371
  sessionParent: sessionFile,
24360
24372
  // Path is driven by sessionParent when under ledger home; cwd is a fallback only.
24361
- cwd: dirname10(sessionFile)
24373
+ cwd: dirname11(sessionFile)
24362
24374
  });
24363
24375
  const { records: records2 } = await readSitianRecords(recordFile);
24364
24376
  for (let i = records2.length - 1; i >= 0; i -= 1) {
@@ -24384,7 +24396,7 @@ async function readSessionProviderStop(sessionFile) {
24384
24396
  }
24385
24397
  }
24386
24398
  async function readBoundEvidenceChildKnownFailure(sessionFile) {
24387
- const childDirectory = join21(dirname10(sessionFile), "evidence-children");
24399
+ const childDirectory = join21(dirname11(sessionFile), "evidence-children");
24388
24400
  let names;
24389
24401
  try {
24390
24402
  names = await readdir5(childDirectory);
@@ -24449,7 +24461,7 @@ async function loadBoundAuditorVolumes(sessionFile) {
24449
24461
  latestParentUserIndex = i;
24450
24462
  break;
24451
24463
  }
24452
- const childDirectories = [join21(dirname10(sessionFile), "auditor-roles")];
24464
+ const childDirectories = [join21(dirname11(sessionFile), "auditor-roles")];
24453
24465
  const valid = [];
24454
24466
  let sawAnyDirectory = false;
24455
24467
  for (const childDirectory of childDirectories) {
@@ -26023,7 +26035,7 @@ function publicationAttemptFromError(path, error) {
26023
26035
  }
26024
26036
  function uniqueFailureFallbackDirs(runDirectory, baseDir) {
26025
26037
  const dirs = [];
26026
- for (const dir of [baseDir, runDirectory, dirname10(runDirectory)]) {
26038
+ for (const dir of [baseDir, runDirectory, dirname11(runDirectory)]) {
26027
26039
  if (!dirs.includes(dir)) dirs.push(dir);
26028
26040
  }
26029
26041
  return dirs;
@@ -26479,7 +26491,7 @@ var init_case_dossier_delivery = __esm({
26479
26491
 
26480
26492
  // src/host-transition-prior-native.ts
26481
26493
  import { access as access2, readdir as readdir6 } from "node:fs/promises";
26482
- import { dirname as dirname11, join as join23 } from "node:path";
26494
+ import { dirname as dirname12, join as join23 } from "node:path";
26483
26495
  function isEnoent2(error) {
26484
26496
  return typeof error === "object" && error !== null && error.code === "ENOENT";
26485
26497
  }
@@ -26493,7 +26505,7 @@ async function listPiNativeRecordPaths(sessionFile) {
26493
26505
  }
26494
26506
  }
26495
26507
  async function listSitianRecordPaths(sessionParent) {
26496
- const sessionRoot = dirname11(sessionParent);
26508
+ const sessionRoot = dirname12(sessionParent);
26497
26509
  let entries;
26498
26510
  try {
26499
26511
  entries = await readdir6(sessionRoot, { withFileTypes: true });
@@ -29875,9 +29887,9 @@ var init_analyst_book_key = __esm({
29875
29887
  // src/atomic-write.ts
29876
29888
  import { randomUUID as randomUUID6 } from "node:crypto";
29877
29889
  import { rename, rm, writeFile as writeFile8 } from "node:fs/promises";
29878
- import { dirname as dirname12, join as join27 } from "node:path";
29890
+ import { dirname as dirname13, join as join27 } from "node:path";
29879
29891
  async function writeFileAtomically(destination, contents) {
29880
- const parent = dirname12(destination);
29892
+ const parent = dirname13(destination);
29881
29893
  const temporary = join27(parent, `.atomic-write-${randomUUID6()}.tmp`);
29882
29894
  try {
29883
29895
  await writeFile8(temporary, contents);
@@ -29895,7 +29907,7 @@ var init_atomic_write = __esm({
29895
29907
 
29896
29908
  // src/analyst-index.ts
29897
29909
  import { open as open3, readFile as readFile17, unlink as unlink4 } from "node:fs/promises";
29898
- import { dirname as dirname13, join as join28 } from "node:path";
29910
+ import { dirname as dirname14, join as join28 } from "node:path";
29899
29911
  function sleep(ms) {
29900
29912
  return new Promise((resolve11) => {
29901
29913
  setTimeout(resolve11, ms);
@@ -29903,8 +29915,8 @@ function sleep(ms) {
29903
29915
  }
29904
29916
  async function withAnalystLibraryIndexLock(ledgerHome, fn) {
29905
29917
  const indexPath = analystLibraryIndexPath(ledgerHome);
29906
- ensureRealDirectoryTree(ledgerHome, dirname13(indexPath));
29907
- const lockPath = join28(dirname13(indexPath), LIBRARY_INDEX_LOCK_NAME);
29918
+ ensureRealDirectoryTree(ledgerHome, dirname14(indexPath));
29919
+ const lockPath = join28(dirname14(indexPath), LIBRARY_INDEX_LOCK_NAME);
29908
29920
  assertLedgerFileInsideHome(lockPath, ledgerHome);
29909
29921
  const startedAt = Date.now();
29910
29922
  while (true) {
@@ -30040,7 +30052,7 @@ async function readAnalystLibraryIndexPage(ledgerHome) {
30040
30052
  }
30041
30053
  async function writeAnalystLibraryIndexPage(ledgerHome, page) {
30042
30054
  const path = analystLibraryIndexPath(ledgerHome);
30043
- ensureRealDirectoryTree(ledgerHome, dirname13(path));
30055
+ ensureRealDirectoryTree(ledgerHome, dirname14(path));
30044
30056
  assertLedgerFileInsideHome(path, ledgerHome);
30045
30057
  await writeFileAtomically(path, `${JSON.stringify(page, null, 2)}
30046
30058
  `);
@@ -31417,7 +31429,7 @@ var init_analyst_metric_family = __esm({
31417
31429
 
31418
31430
  // src/analyst-page.ts
31419
31431
  import { createHash as createHash5 } from "node:crypto";
31420
- import { dirname as dirname14, join as join30 } from "node:path";
31432
+ import { dirname as dirname15, join as join30 } from "node:path";
31421
31433
  function analystIssuePageKey(address) {
31422
31434
  const parts = ["book", address.bookKey];
31423
31435
  if (address.issueNumber !== void 0) {
@@ -31547,7 +31559,7 @@ async function buildAnalystIssueMetricsPage(input) {
31547
31559
  }
31548
31560
  async function writeAnalystIssueMetricsPage(ledgerHome, page) {
31549
31561
  const path = analystIssuePagePath(ledgerHome, analystIssuePageAddressFromPage(page));
31550
- ensureRealDirectoryTree(ledgerHome, dirname14(path));
31562
+ ensureRealDirectoryTree(ledgerHome, dirname15(path));
31551
31563
  assertLedgerFileInsideHome(path, ledgerHome);
31552
31564
  await writeFileAtomically(path, `${JSON.stringify(page, null, 2)}
31553
31565
  `);
@@ -33065,7 +33077,7 @@ var init_cli = __esm({
33065
33077
 
33066
33078
  // src/public-cli/main.ts
33067
33079
  import { existsSync as existsSync8 } from "node:fs";
33068
- import { dirname as dirname15, join as join32 } from "node:path";
33080
+ import { dirname as dirname16, join as join32 } from "node:path";
33069
33081
  import { fileURLToPath as fileURLToPath2 } from "node:url";
33070
33082
 
33071
33083
  // src/public-cli/host-pi-runtime.ts
@@ -33152,7 +33164,7 @@ function linkPackage(packageRoot2, name, targetDir) {
33152
33164
  }
33153
33165
 
33154
33166
  // src/public-cli/main.ts
33155
- var here = dirname15(fileURLToPath2(import.meta.url));
33167
+ var here = dirname16(fileURLToPath2(import.meta.url));
33156
33168
  function resolvePackageRoot(binDir) {
33157
33169
  const canonical = join32(binDir, "..", "..");
33158
33170
  if (existsSync8(join32(canonical, "package.json"))) {
@@ -748,17 +748,14 @@ export function createRoleRuntimeExtension(dependencies) {
748
748
  const role = roleHost.getFlag(ROLE_FLAG.name);
749
749
  if (role !== undefined && !admitted)
750
750
  return { action: "handled" };
751
- // Envelope exclusively owns Reviewer Skill invocation transform + original-request capture.
751
+ // Reviewer: recover original request; Pi argv may already carry native form.
752
752
  if (role === "reviewer"
753
753
  && admitted
754
754
  && activeReviewerParent !== undefined
755
755
  && reviewerOriginalRequest === undefined) {
756
- reviewerOriginalRequest = event.text;
757
- return {
758
- action: "transform",
759
- text: activeReviewerParent.skillBinding.invocation(event.text),
760
- ...(event.images === undefined ? {} : { images: event.images }),
761
- };
756
+ reviewerOriginalRequest =
757
+ roleHost.capabilities?.skillOriginalRequest?.(activeReviewerParent.skillBinding.name, event.text) ?? event.text;
758
+ return { action: "continue" };
762
759
  }
763
760
  return { action: "continue" };
764
761
  });
@@ -227,6 +227,12 @@ export function createCoderRoleRuntime(pi, dependencies, hostActions) {
227
227
  });
228
228
  return {
229
229
  async activate(ctx) {
230
+ // Each activation owns its own Skill capture state; prior-session flags must
231
+ // not authorize a later apply completed (same RoleHost, sequential activate).
232
+ tddInvocationInjected = false;
233
+ originalRequest = undefined;
234
+ expansionPending = false;
235
+ expansionCaptured = false;
230
236
  soul = (await dependencies.loadSoul()).trim();
231
237
  if (soul.length === 0)
232
238
  throw new Error("Coder soul is empty");
@@ -303,18 +309,14 @@ export function createCoderRoleRuntime(pi, dependencies, hostActions) {
303
309
  }
304
310
  tddInvocationInjected = true;
305
311
  expansionPending = true;
306
- const isNativeTdd = event.text === "/skill:tdd" ||
307
- event.text.startsWith("/skill:tdd ");
308
- if (isNativeTdd) {
309
- originalRequest = event.text.slice("/skill:tdd".length).trim();
310
- return { action: "continue" };
311
- }
312
- originalRequest = event.text.trim();
313
- return {
314
- action: "transform",
315
- text: binding?.invocation(event.text) ?? `/skill:tdd ${event.text}`,
316
- ...(event.images === undefined ? {} : { images: event.images }),
317
- };
312
+ // Original request via host capability when Pi argv already carries native form;
313
+ // non-pi keeps plain original bytes (no consumer trim; #822 r3 / reviewer-aligned).
314
+ // Role never emits or parses `/skill:` (ADR 0082).
315
+ originalRequest = binding === undefined
316
+ ? event.text
317
+ : (pi.capabilities?.skillOriginalRequest?.(binding.name, event.text)
318
+ ?? event.text);
319
+ return { action: "continue" };
318
320
  });
319
321
  pi.on("before_agent_start", (event, ctx) => {
320
322
  if (soul === undefined)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@akagilnc/pi-workflow-roles",
3
- "version": "0.1.3826",
3
+ "version": "0.1.3837",
4
4
  "description": "Soul-bound workflow roles for Pi",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -41,27 +41,26 @@ type RpcRequest = { readonly id: number; readonly token: string; readonly method
41
41
  type ToolCallParams = { readonly name?: unknown; readonly arguments?: unknown };
42
42
  type ContentPart = { type: "text"; text: string } | { type: "image"; data: string; mimeType: string };
43
43
 
44
- /** Parse the canonical Skill invocation produced by the shared input transform. */
45
- export function parseCanonicalSkillInvocation(prompt: string): { readonly name: string; readonly userMessage: string } | undefined {
46
- const match = /^\/skill:([A-Za-z0-9_-]+)(?:\s+([\s\S]*))?$/s.exec(prompt.trim());
47
- if (match === null) return undefined;
48
- return { name: match[1]!, userMessage: (match[2] ?? "").trim() };
49
- }
50
-
51
- /** Build host-side Skill expansion evidence from pre-read RoleTurnRequest.methods. */
44
+ /**
45
+ * Build host-side Skill expansion evidence from pre-read RoleTurnRequest.methods.
46
+ * Non-pi hosts never parse Pi `/skill:` syntax (ADR 0082 `pi-no-privilege`);
47
+ * a single bound method treats the plain prompt as the preserved user message.
48
+ * Pi-native slash forms stay inside `src/pi/` only.
49
+ */
52
50
  export function buildAcpSkillExpansion(
53
51
  methodSkills: ReadonlyMap<string, { readonly path: string; readonly body: string }>,
54
52
  prompt: string,
55
53
  ): HostSkillExpansionEvidence | undefined {
56
- const parsed = parseCanonicalSkillInvocation(prompt);
57
- if (parsed === undefined) return undefined;
58
- const method = methodSkills.get(parsed.name);
59
- if (method === undefined) return undefined;
54
+ if (methodSkills.size !== 1) return undefined;
55
+ const entry = methodSkills.entries().next().value;
56
+ if (entry === undefined) return undefined;
57
+ const [name, method] = entry;
60
58
  return Object.freeze({
61
- name: parsed.name,
59
+ name,
62
60
  location: method.path,
63
61
  content: `References are relative to ${dirname(method.path)}.\n\n${method.body}`,
64
- userMessage: parsed.userMessage,
62
+ // Non-pi typed chain keeps original task bytes (ticket #822 r3); no consumer trim.
63
+ userMessage: prompt,
65
64
  });
66
65
  }
67
66
 
@@ -26,7 +26,7 @@ export type AcpPreparedTurn = Readonly<{
26
26
  * send path — never a test-only parallel face.
27
27
  */
28
28
  systemPrompt: { readonly body: string; readonly materials: readonly unknown[] };
29
- /** Effective user prompt after host-side input transform (canonical Skill invocation). */
29
+ /** Effective user prompt after host-side input handling (host-neutral; no Pi `/skill:` syntax). */
30
30
  prompt: string;
31
31
  /**
32
32
  * Host abort signal armed only by typed infrastructure failure (envelope
@@ -25,7 +25,10 @@ export type CanonicalSkillEvidence<Name extends CanonicalSkillName = CanonicalSk
25
25
  export type CanonicalSkillBinding<Name extends CanonicalSkillName = CanonicalSkillName> = Readonly<{
26
26
  name: Name;
27
27
  snapshot: CanonicalSkillSnapshot;
28
- invocation(originalRequest: string): string;
28
+ /**
29
+ * Accept host expansion evidence when name/location/content match the binding
30
+ * and userMessage equals the captured original request (ADR 0032).
31
+ */
29
32
  captureExpansion(
30
33
  evidence: HostSkillExpansionEvidence | undefined,
31
34
  originalRequest: string,
@@ -100,9 +103,6 @@ export async function loadCanonicalSkillBinding(
100
103
  const binding: CanonicalSkillBinding<typeof name> = {
101
104
  name,
102
105
  snapshot,
103
- invocation(originalRequest) {
104
- return `/skill:${name} ${originalRequest}`;
105
- },
106
106
  captureExpansion(evidence, originalRequest) {
107
107
  return captureCanonicalSkillExpansion(
108
108
  name,
@@ -321,6 +321,12 @@ export type HostSkillExpansionEvidence = Readonly<{
321
321
  /** Host capability declaration (contract verb ④). */
322
322
  export type HostCapabilityDeclaration = Readonly<{
323
323
  skillExpansion(prompt: string): HostSkillExpansionEvidence | undefined;
324
+ /**
325
+ * Pi-only: recover the plain original request from a native skill turn text
326
+ * (argv may already carry `/skill:<name>` from the Pi adapter). Absent on
327
+ * non-pi hosts so role body never parses Pi slash syntax (ADR 0082).
328
+ */
329
+ skillOriginalRequest?(name: string, text: string): string;
324
330
  }>;
325
331
 
326
332
  /** Host-owned effects used by the shared activation envelope. */
@@ -46,9 +46,6 @@ export async function loadPackagedCanonicalSkillBinding<
46
46
  const binding: CanonicalSkillBinding<Name> = {
47
47
  name,
48
48
  snapshot,
49
- invocation(originalRequest) {
50
- return `/skill:${name} ${originalRequest}`;
51
- },
52
49
  captureExpansion(evidence, originalRequest) {
53
50
  return captureCanonicalSkillExpansion(
54
51
  name,
package/src/pi/adapter.ts CHANGED
@@ -157,6 +157,19 @@ export function createPiRoleHostAdapter(
157
157
  userMessage,
158
158
  });
159
159
  },
160
+ /** Recover plain original request from Pi-native `/skill:` turn text (ADR 0082). */
161
+ skillOriginalRequest(name, text) {
162
+ const token = `/skill:${name}`;
163
+ const trimmed = text.trimStart();
164
+ if (
165
+ trimmed === token
166
+ || trimmed.startsWith(`${token} `)
167
+ || trimmed.startsWith(`${token}\n`)
168
+ ) {
169
+ return text.slice(text.indexOf(token) + token.length).trim();
170
+ }
171
+ return text.trim();
172
+ },
160
173
  },
161
174
  registerFlag: (name, definition) => pi.registerFlag(name, definition),
162
175
  getFlag: (name) => pi.getFlag(name),
@@ -6,7 +6,7 @@
6
6
  import { execFile, spawn } from "node:child_process";
7
7
  import { constants } from "node:fs";
8
8
  import { access, appendFile, readFile, realpath } from "node:fs/promises";
9
- import { delimiter, isAbsolute, join, resolve } from "node:path";
9
+ import { basename, delimiter, dirname, isAbsolute, join, resolve } from "node:path";
10
10
  import { platform } from "node:process";
11
11
  import { promisify } from "node:util";
12
12
  import { randomUUID } from "node:crypto";
@@ -167,9 +167,35 @@ function buildMethodArgs(methods: readonly MethodBinding[]): string[] {
167
167
  return skillArgs;
168
168
  }
169
169
 
170
+ /**
171
+ * Pi-native `/skill:<name>` for a single forced method (ADR 0082 `pi-no-privilege`).
172
+ * Driven by typed RoleTurnRequest.methods — middle-layer decision, Pi-only syntax.
173
+ * Zero/many methods leave the prompt alone (Fixer optional pair stays `--skill` only).
174
+ */
175
+ export function applyPiNativeSkillInvocation(
176
+ methods: readonly MethodBinding[],
177
+ prompt: string,
178
+ ): string {
179
+ const skills = methods.filter((method) => method.kind === "skill");
180
+ if (skills.length !== 1) return prompt;
181
+ const name = basename(dirname(skills[0]!.path));
182
+ if (name.length === 0) return prompt;
183
+ const token = `/skill:${name}`;
184
+ const trimmed = prompt.trimStart();
185
+ if (
186
+ trimmed === token
187
+ || trimmed.startsWith(`${token} `)
188
+ || trimmed.startsWith(`${token}\n`)
189
+ ) {
190
+ return prompt;
191
+ }
192
+ return prompt.length === 0 ? token : `${token} ${prompt}`;
193
+ }
194
+
170
195
  /**
171
196
  * Translate a closed RoleTurnRequest into Pi argv after `--no-extensions -e entry`.
172
197
  * Controlled session constants and session coordinates are adapter-internal.
198
+ * Single forced method → Pi-native `/skill:` on the argv prompt (coder/reviewer/merger).
173
199
  */
174
200
  export function buildPiTurnExtraArgs(
175
201
  request: RoleTurnRequest,
@@ -177,13 +203,14 @@ export function buildPiTurnExtraArgs(
177
203
  extraPiArgs: readonly string[] = [],
178
204
  ): string[] {
179
205
  const { sessionFile, sessionDirectory } = authority.decode(request.principal);
180
- const prompt =
206
+ const rawPrompt =
181
207
  request.continuation.kind === "initial" || request.continuation.kind === "resume"
182
208
  ? request.continuation.prompt
183
209
  : (() => {
184
210
  const _exhaustive: never = request.continuation;
185
211
  return _exhaustive;
186
212
  })();
213
+ const prompt = applyPiNativeSkillInvocation(request.methods, rawPrompt);
187
214
  return [
188
215
  "--no-skills",
189
216
  ...buildMethodArgs(request.methods),
@@ -3071,16 +3071,15 @@ export async function admitMergerInvocation(
3071
3071
  }
3072
3072
 
3073
3073
  /**
3074
- * Build the Pi prompt transport for an admitted Merger request.
3075
- * Every invocation forces package merge-only method expansion before conflict work.
3074
+ * Build the host-neutral prompt transport for an admitted Merger request.
3075
+ * Method material binds on RoleTurnRequest.methods; Pi-native `/skill:` form
3076
+ * is adapter-internal only (ADR 0082).
3076
3077
  */
3077
3078
  export function buildMergerTransportPrompt(
3078
3079
  admitted: AdmittedMergerInvocation,
3079
3080
  engineMaterial?: EngineSessionMaterial,
3080
3081
  ): string {
3081
- const lines: string[] = [
3082
- `/skill:resolving-merge-conflicts ${admitted.instruction}`,
3083
- ];
3082
+ const lines: string[] = [admitted.instruction];
3084
3083
  if (admitted.attachments.length > 0) {
3085
3084
  lines.push("");
3086
3085
  lines.push("已受理附件(冻结快照路径):");
@@ -1227,19 +1227,19 @@ export function createRoleRuntimeExtension(
1227
1227
  roleHost.on("input", (event) => {
1228
1228
  const role = roleHost.getFlag(ROLE_FLAG.name);
1229
1229
  if (role !== undefined && !admitted) return { action: "handled" as const };
1230
- // Envelope exclusively owns Reviewer Skill invocation transform + original-request capture.
1230
+ // Reviewer: recover original request; Pi argv may already carry native form.
1231
1231
  if (
1232
1232
  role === "reviewer"
1233
1233
  && admitted
1234
1234
  && activeReviewerParent !== undefined
1235
1235
  && reviewerOriginalRequest === undefined
1236
1236
  ) {
1237
- reviewerOriginalRequest = event.text;
1238
- return {
1239
- action: "transform" as const,
1240
- text: activeReviewerParent.skillBinding.invocation(event.text),
1241
- ...(event.images === undefined ? {} : { images: event.images }),
1242
- };
1237
+ reviewerOriginalRequest =
1238
+ roleHost.capabilities?.skillOriginalRequest?.(
1239
+ activeReviewerParent.skillBinding.name,
1240
+ event.text,
1241
+ ) ?? event.text;
1242
+ return { action: "continue" as const };
1243
1243
  }
1244
1244
  return { action: "continue" as const };
1245
1245
  });
@@ -347,6 +347,12 @@ export function createCoderRoleRuntime(
347
347
 
348
348
  return {
349
349
  async activate(ctx) {
350
+ // Each activation owns its own Skill capture state; prior-session flags must
351
+ // not authorize a later apply completed (same RoleHost, sequential activate).
352
+ tddInvocationInjected = false;
353
+ originalRequest = undefined;
354
+ expansionPending = false;
355
+ expansionCaptured = false;
350
356
  soul = (await dependencies.loadSoul()).trim();
351
357
  if (soul.length === 0) throw new Error("Coder soul is empty");
352
358
  const selectedPhase = pi.getFlag("ak-coder-phase");
@@ -433,19 +439,14 @@ export function createCoderRoleRuntime(
433
439
  }
434
440
  tddInvocationInjected = true;
435
441
  expansionPending = true;
436
- const isNativeTdd =
437
- event.text === "/skill:tdd" ||
438
- event.text.startsWith("/skill:tdd ");
439
- if (isNativeTdd) {
440
- originalRequest = event.text.slice("/skill:tdd".length).trim();
441
- return { action: "continue" as const };
442
- }
443
- originalRequest = event.text.trim();
444
- return {
445
- action: "transform" as const,
446
- text: binding?.invocation(event.text) ?? `/skill:tdd ${event.text}`,
447
- ...(event.images === undefined ? {} : { images: event.images }),
448
- };
442
+ // Original request via host capability when Pi argv already carries native form;
443
+ // non-pi keeps plain original bytes (no consumer trim; #822 r3 / reviewer-aligned).
444
+ // Role never emits or parses `/skill:` (ADR 0082).
445
+ originalRequest = binding === undefined
446
+ ? event.text
447
+ : (pi.capabilities?.skillOriginalRequest?.(binding.name, event.text)
448
+ ?? event.text);
449
+ return { action: "continue" as const };
449
450
  });
450
451
  pi.on("before_agent_start", (event, ctx) => {
451
452
  if (soul === undefined) throw new Error("将作监职分未装载");