@astrosheep/keiyaku 4.6.6 → 4.6.7

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.
Files changed (68) hide show
  1. package/README.md +7 -0
  2. package/build/{integrations/marketplace → plugins}/.agents/plugins/marketplace.json +1 -1
  3. package/build/plugins/.claude-plugin/marketplace.json +5 -0
  4. package/build/{integrations/marketplace/plugins/keiyaku → plugins/claude}/.claude-plugin/plugin.json +1 -1
  5. package/build/{integrations/marketplace/plugins/keiyaku → plugins/codex}/.codex-plugin/plugin.json +1 -1
  6. package/build/plugins/codex/skills/keiyaku/SKILL.md +44 -0
  7. package/build/plugins/codex/skills/keiyaku-akuma/SKILL.md +130 -0
  8. package/build/plugins/codex/skills/keiyaku-akuma/references/automation.md +269 -0
  9. package/build/plugins/codex/skills/keiyaku-babysit/SKILL.md +65 -0
  10. package/build/plugins/codex/skills/keiyaku-bind/SKILL.md +216 -0
  11. package/build/plugins/codex/skills/keiyaku-bind/agents/openai.yaml +4 -0
  12. package/build/plugins/codex/skills/keiyaku-task/SKILL.md +104 -0
  13. package/build/plugins/codex/skills/keiyaku-workflow/SKILL.md +129 -0
  14. package/build/plugins/opencode/package.json +6 -0
  15. package/build/plugins/opencode/skills/keiyaku/SKILL.md +44 -0
  16. package/build/plugins/opencode/skills/keiyaku-akuma/SKILL.md +130 -0
  17. package/build/plugins/opencode/skills/keiyaku-akuma/references/automation.md +269 -0
  18. package/build/plugins/opencode/skills/keiyaku-babysit/SKILL.md +65 -0
  19. package/build/plugins/opencode/skills/keiyaku-bind/SKILL.md +216 -0
  20. package/build/plugins/opencode/skills/keiyaku-bind/agents/openai.yaml +4 -0
  21. package/build/plugins/opencode/skills/keiyaku-task/SKILL.md +104 -0
  22. package/build/plugins/opencode/skills/keiyaku-workflow/SKILL.md +129 -0
  23. package/build/skills/keiyaku/SKILL.md +44 -0
  24. package/build/skills/keiyaku-akuma/SKILL.md +130 -0
  25. package/build/skills/keiyaku-akuma/references/automation.md +269 -0
  26. package/build/skills/keiyaku-babysit/SKILL.md +65 -0
  27. package/build/skills/keiyaku-bind/SKILL.md +216 -0
  28. package/build/skills/keiyaku-bind/agents/openai.yaml +4 -0
  29. package/build/skills/keiyaku-task/SKILL.md +104 -0
  30. package/build/skills/keiyaku-workflow/SKILL.md +129 -0
  31. package/build/src/akuma/akuma-instance.js +7 -5
  32. package/build/src/akuma/akuma-observe.d.ts +4 -0
  33. package/build/src/akuma/akuma-observe.js +12 -0
  34. package/build/src/akuma/akuma-owner.d.ts +3 -2
  35. package/build/src/akuma/akuma-owner.js +28 -10
  36. package/build/src/akuma/akuma-selection-execution.d.ts +3 -1
  37. package/build/src/akuma/akuma-selection-execution.js +2 -1
  38. package/build/src/akuma/akuma-wait.d.ts +2 -1
  39. package/build/src/akuma/akuma-wait.js +4 -1
  40. package/build/src/akuma/body.d.ts +1 -0
  41. package/build/src/akuma/body.js +12 -23
  42. package/build/src/akuma/selection-owner-port.d.ts +1 -1
  43. package/build/src/akuma/selection-owner-port.js +16 -5
  44. package/build/src/akuma/selection-request.d.ts +2 -0
  45. package/build/src/akuma/selection-request.js +2 -1
  46. package/build/src/akuma-body.js +4 -2
  47. package/build/src/cli/commands/akuma.js +6 -12
  48. package/build/src/cli/commands/install.js +3 -3
  49. package/build/src/library/akuma-creation.js +21 -19
  50. package/build/src/library/selection.js +5 -2
  51. package/build/src/plugin/akuma-signals.d.ts +10 -11
  52. package/build/src/plugin/akuma-signals.js +36 -20
  53. package/build/src/plugin/public.d.ts +5 -0
  54. package/build/src/plugin/runtime.d.ts +1 -0
  55. package/build/src/plugin/runtime.js +34 -23
  56. package/package.json +7 -7
  57. package/build/integrations/marketplace/.claude-plugin/marketplace.json +0 -5
  58. package/build/integrations/marketplace/plugins/keiyaku/package.json +0 -17
  59. /package/build/{integrations/marketplace/plugins/keiyaku → plugins/claude}/skills/keiyaku/SKILL.md +0 -0
  60. /package/build/{integrations/marketplace/plugins/keiyaku → plugins/claude}/skills/keiyaku-akuma/SKILL.md +0 -0
  61. /package/build/{integrations/marketplace/plugins/keiyaku → plugins/claude}/skills/keiyaku-akuma/references/automation.md +0 -0
  62. /package/build/{integrations/marketplace/plugins/keiyaku → plugins/claude}/skills/keiyaku-babysit/SKILL.md +0 -0
  63. /package/build/{integrations/marketplace/plugins/keiyaku → plugins/claude}/skills/keiyaku-bind/SKILL.md +0 -0
  64. /package/build/{integrations/marketplace/plugins/keiyaku → plugins/claude}/skills/keiyaku-bind/agents/openai.yaml +0 -0
  65. /package/build/{integrations/marketplace/plugins/keiyaku → plugins/claude}/skills/keiyaku-task/SKILL.md +0 -0
  66. /package/build/{integrations/marketplace/plugins/keiyaku → plugins/claude}/skills/keiyaku-workflow/SKILL.md +0 -0
  67. /package/build/{integrations/marketplace/plugins/keiyaku/opencode.js → plugins/opencode/index.js} +0 -0
  68. /package/build/{integrations → plugins}/pi/keiyaku.ts +0 -0
@@ -64,7 +64,7 @@ export function decodeAskObservation(observation, schema) {
64
64
  }
65
65
  }
66
66
  export async function observeAdmittedAskAkuma(input) {
67
- const owner = new AkumaOwner(input.id, input.path);
67
+ const owner = new AkumaOwner(input.id, input.path, input.outcomeObserved);
68
68
  const tell = input.receipt ?? (await owner.admittedReceipt(input.tellId));
69
69
  await input.onObserve?.admitted?.(tell, input.id);
70
70
  let settled;
@@ -118,6 +118,7 @@ export async function executeAskAkuma(input) {
118
118
  wake: admission.wake,
119
119
  ...(input.signal === undefined ? {} : { signal: input.signal }),
120
120
  ...(input.onObserve === undefined ? {} : { onObserve: input.onObserve }),
121
+ ...(input.outcomeObserved === undefined ? {} : { outcomeObserved: input.outcomeObserved }),
121
122
  });
122
123
  }
123
124
  export async function executeKillAkuma(input) {
@@ -1,6 +1,6 @@
1
1
  import type { AkumaAlias } from "../identity/selector.js";
2
2
  import type { WorldRoot } from "../world.js";
3
- import { type WaitReason } from "./akuma-observe.js";
3
+ import { type OutcomeObserver, type WaitReason } from "./akuma-observe.js";
4
4
  import type { AkumaStatus } from "./akuma.js";
5
5
  import type { DispatchAssociation } from "./dispatch-association.js";
6
6
  import type { ActivityRow } from "./projection.js";
@@ -33,6 +33,7 @@ type WaitExecutionInput = Readonly<{
33
33
  signal?: AbortSignal;
34
34
  /** Internal custom completion judge over the full final observation where supplied. */
35
35
  predicate?: (status: AkumaStatus) => boolean;
36
+ outcomeObserved?: OutcomeObserver;
36
37
  /** Resolves one Akuma's identity facts; consulted once per Akuma a viewer first sees. */
37
38
  identity?: (id: AkumaStatus["id"]) => Promise<WaitIdentityFacts>;
38
39
  /** The selected set in the caller's order; a viewer's head and scoreboard follow it. */
@@ -1,5 +1,5 @@
1
1
  import { AkumaNotBornError } from "./akuma-errors.js";
2
- import { createLiveStatusReader, defaultWaitComplete, readLiveStatus, readWaitComplete, waitForObservation, } from "./akuma-observe.js";
2
+ import { createLiveStatusReader, defaultWaitComplete, readLiveStatus, readWaitComplete, observeOutcomeRows, observeStatusOutcome, waitForObservation, } from "./akuma-observe.js";
3
3
  import { pathsForAkuId } from "./identity.js";
4
4
  const SHARED_ORDINARY_BUDGET = 30;
5
5
  async function observeWaitRound(path, ids, signal, readers) {
@@ -88,6 +88,9 @@ export async function executeWaitAkuma(input) {
88
88
  input.onSelected(ordered.map((id) => ({ id, ...facts.get(id) })));
89
89
  }
90
90
  const observeRound = async (round) => {
91
+ await Promise.all(round.observations.map(({ status, rows }) => input.observe === undefined
92
+ ? observeStatusOutcome(status, input.outcomeObserved)
93
+ : observeOutcomeRows(status.id, rows, input.outcomeObserved)));
91
94
  if (input.observe === undefined)
92
95
  return;
93
96
  const observed = [];
@@ -93,6 +93,7 @@ type BodyRuntime = Readonly<{
93
93
  world: WorldRoot;
94
94
  externalCommands: Readonly<Record<string, ErasedRequestCommand>>;
95
95
  }>;
96
+ export declare function recordPluginDiagnostic(paths: AkumaPaths, occurrence: string, error: unknown): Promise<void>;
96
97
  export declare function bodyProcessInput(launch: BodyLaunch, bodyModuleUrl?: string): Promise<{
97
98
  argv: string[];
98
99
  cwd: string;
@@ -10,7 +10,7 @@ import { HeldAkumaLeash, breakBody, decidePendingTellDisposition, drainPendingTe
10
10
  import { akumaPathsSchema, pathsForAkuId, worldRootForAkumaPaths } from "./identity.js";
11
11
  import { admitCallInitialTell, } from "./call-initial-tell.js";
12
12
  import { akumaExecutionEnvironment } from "./providers/execution-environment.js";
13
- import { pluginRuntime } from "../plugin/runtime.js";
13
+ import { emitPluginSignal } from "../plugin/akuma-signals.js";
14
14
  import { World } from "../world.js";
15
15
  import { resolveProviderExecution } from "./providers/index.js";
16
16
  import { clearBodyRequestTransport, settleBodyRequests } from "./request-serve.js";
@@ -156,7 +156,7 @@ function boundedDiagnostic(error) {
156
156
  const text = diagnostic(error);
157
157
  return text.length <= 500 ? text : `${text.slice(0, 500)}...`;
158
158
  }
159
- async function recordPluginDiagnostic(paths, occurrence, error) {
159
+ export async function recordPluginDiagnostic(paths, occurrence, error) {
160
160
  try {
161
161
  await appendFile(paths.log, `plugin ${occurrence} failed: ${boundedDiagnostic(error)}\n`);
162
162
  }
@@ -306,8 +306,7 @@ async function recoverBodyRequests(input) {
306
306
  }
307
307
  return false;
308
308
  }
309
- function turnOutcomeEmitter(launch, soul) {
310
- let plugins;
309
+ function turnOutcomeEmitter(launch, soul, world) {
311
310
  const reportDiagnostic = (message) => {
312
311
  void recordPluginDiagnostic(launch.paths, "turn outcome", message);
313
312
  };
@@ -318,11 +317,7 @@ function turnOutcomeEmitter(launch, soul) {
318
317
  if (turn === null)
319
318
  throw new Error(`Committed Turn ${turnSequence} is missing from Heart`);
320
319
  const participants = await readTurnParticipants(launch.paths, turnSequence);
321
- plugins ??= pluginRuntime({
322
- world: await World.at(worldRootForAkumaPaths(launch.paths)),
323
- reportDiagnostic,
324
- });
325
- await (await plugins).emit({
320
+ await emitPluginSignal({ world, reportDiagnostic }, {
326
321
  kind: "akuma.turn-outcome",
327
322
  akumaId: soul.id,
328
323
  bodySequence: turn.bodySequence,
@@ -333,7 +328,7 @@ function turnOutcomeEmitter(launch, soul) {
333
328
  ? { kind: "answered", text: outcome.answer }
334
329
  : { kind: "failed", reason: outcome.diagnostic },
335
330
  ...(launch.completion?.contractId === undefined ? {} : { contractId: launch.completion.contractId }),
336
- }, reportDiagnostic);
331
+ });
337
332
  }
338
333
  catch (error) {
339
334
  await recordPluginDiagnostic(launch.paths, "turn outcome", error);
@@ -341,22 +336,16 @@ function turnOutcomeEmitter(launch, soul) {
341
336
  },
342
337
  };
343
338
  }
344
- function bodyEndEmitter(launch, soul) {
345
- let plugins;
339
+ function bodyEndEmitter(launch, soul, world) {
346
340
  const reportDiagnostic = (message) => {
347
341
  void recordPluginDiagnostic(launch.paths, "body end", message);
348
342
  };
349
- return (bodySequence, end, diagnostic) => (async () => {
343
+ return async (bodySequence, end, diagnostic) => {
350
344
  try {
351
345
  const latestTurn = await readLatestTurnForBody(launch.paths, bodySequence);
352
346
  const initiator = latestTurn?.initiator;
353
347
  const participants = latestTurn === null ? [] : await readTurnParticipants(launch.paths, latestTurn.sequence);
354
- plugins ??= pluginRuntime({
355
- world: await World.at(worldRootForAkumaPaths(launch.paths)),
356
- reportDiagnostic,
357
- });
358
- const runtime = await plugins;
359
- await runtime.emit({
348
+ await emitPluginSignal({ world, reportDiagnostic }, {
360
349
  kind: "akuma.body-ended",
361
350
  akumaId: soul.id,
362
351
  bodySequence,
@@ -365,12 +354,12 @@ function bodyEndEmitter(launch, soul) {
365
354
  ...(participants.length === 0 ? {} : { participants }),
366
355
  ...(diagnostic === undefined ? {} : { diagnostic }),
367
356
  ...(launch.completion?.contractId === undefined ? {} : { contractId: launch.completion.contractId }),
368
- }, reportDiagnostic);
357
+ });
369
358
  }
370
359
  catch (error) {
371
360
  await recordPluginDiagnostic(launch.paths, "body end", error);
372
361
  }
373
- })();
362
+ };
374
363
  }
375
364
  async function admitInitialCallTell(runtime, input) {
376
365
  const paths = pathsForAkuId(runtime.world, input.id);
@@ -575,8 +564,8 @@ export async function driveAkumaBody(launch, adapter, runtimeInput = {}) {
575
564
  const prepared = await prepareBodyExecution(launch, leash, runtime, adapter);
576
565
  if (prepared === null)
577
566
  return;
578
- emitBodyEnd = bodyEndEmitter(launch, prepared.soul);
579
- emitTurnOutcome = turnOutcomeEmitter(launch, prepared.soul);
567
+ emitBodyEnd = bodyEndEmitter(launch, prepared.soul, world);
568
+ emitTurnOutcome = turnOutcomeEmitter(launch, prepared.soul, world);
580
569
  const body = await leash.recordBody(launch.paths, { leashTakenAt: runtime.now() });
581
570
  bodySequence = body.sequence;
582
571
  const supervisor = await BodySupervisor.open(launch.paths, body.sequence, leash);
@@ -9,4 +9,4 @@ import type { SelectionRequestPort } from "./selection-request.js";
9
9
  * unaddressable member never starts; the executor's own access to the target is
10
10
  * then the product act, never a second addressing probe.
11
11
  */
12
- export declare function selectionRequestPort(world: WorldRoot): SelectionRequestPort;
12
+ export declare function selectionRequestPort(world: WorldRoot, reportDiagnostic: (message: string) => void): SelectionRequestPort;
@@ -1,3 +1,4 @@
1
+ import { outcomePluginObserver } from "../plugin/akuma-signals.js";
1
2
  import { requireBornAkuma } from "./akuma-probe.js";
2
3
  import { executeAskAkuma, executeKillAkuma, executeTellAkuma } from "./akuma-selection-execution.js";
3
4
  import { executeWaitAkuma } from "./akuma-wait.js";
@@ -11,26 +12,36 @@ import { akumaObservationOf } from "./selection-observation.js";
11
12
  * unaddressable member never starts; the executor's own access to the target is
12
13
  * then the product act, never a second addressing probe.
13
14
  */
14
- export function selectionRequestPort(world) {
15
+ export function selectionRequestPort(world, reportDiagnostic) {
15
16
  const prove = async (targets) => {
16
17
  for (const id of targets)
17
18
  await requireBornAkuma(world, id);
18
19
  };
19
20
  return {
20
- wait: async ({ targets, ...request }) => {
21
+ wait: async ({ targets, observer, ...request }) => {
21
22
  await prove(targets);
22
23
  // The lower wait owner returns association-free native evidence; the
23
24
  // actual request crossing composes its canonical transport shape.
24
- const native = await executeWaitAkuma({ path: world, ids: targets, ...request });
25
+ const native = await executeWaitAkuma({
26
+ path: world,
27
+ ids: targets,
28
+ ...request,
29
+ outcomeObserved: outcomePluginObserver({ world, reportDiagnostic }, observer),
30
+ });
25
31
  return { ...native, observations: native.observations.map(akumaObservationOf) };
26
32
  },
27
33
  tell: async ({ target, ...request }) => {
28
34
  await prove([target]);
29
35
  return await executeTellAkuma({ path: world, id: target, ...request });
30
36
  },
31
- ask: async ({ target, ...request }) => {
37
+ ask: async ({ target, observer, ...request }) => {
32
38
  await prove([target]);
33
- return await executeAskAkuma({ path: world, id: target, ...request });
39
+ return await executeAskAkuma({
40
+ path: world,
41
+ id: target,
42
+ ...request,
43
+ outcomeObserved: outcomePluginObserver({ world, reportDiagnostic }, observer),
44
+ });
34
45
  },
35
46
  kill: async ({ targets, ...request }) => {
36
47
  await prove(targets);
@@ -106,6 +106,7 @@ export type SelectionRequestPort = Readonly<{
106
106
  targets: readonly AkumaStatus["id"][];
107
107
  completion: "any" | "all";
108
108
  timeoutMs?: number;
109
+ observer: string;
109
110
  signal: AbortSignal;
110
111
  }>): Promise<AkumaWaitResult>;
111
112
  tell(input: Readonly<{
@@ -126,6 +127,7 @@ export type SelectionRequestPort = Readonly<{
126
127
  schemaJson?: string;
127
128
  initiator?: string;
128
129
  interrupt?: boolean;
130
+ observer: string;
129
131
  signal: AbortSignal;
130
132
  }>): Promise<AkumaAskResult>;
131
133
  kill(input: Readonly<{
@@ -167,7 +167,7 @@ export function selectionRequestCommand(action, port) {
167
167
  if (request.action === "akuma.wait") {
168
168
  return {
169
169
  kind: "served",
170
- result: await port.wait({ ...request, signal: facts.signal }),
170
+ result: await port.wait({ ...request, observer: facts.requester, signal: facts.signal }),
171
171
  service: { action: request.action },
172
172
  };
173
173
  }
@@ -196,6 +196,7 @@ export function selectionRequestCommand(action, port) {
196
196
  body: request.body,
197
197
  tellId: facts.id,
198
198
  recordedAt: facts.admittedAt,
199
+ observer: facts.requester,
199
200
  ...(request.timeoutMs === undefined ? {} : { timeoutMs: request.timeoutMs }),
200
201
  ...(request.schemaJson === undefined ? {} : { schemaJson: request.schemaJson }),
201
202
  ...(request.initiator === undefined ? {} : { initiator: request.initiator }),
@@ -1,6 +1,6 @@
1
1
  import { resolve } from "node:path";
2
2
  import { fileURLToPath } from "node:url";
3
- import { bodyLaunchSchema, LEASH_HELD_EXIT, runAkumaBody } from "./akuma/body.js";
3
+ import { bodyLaunchSchema, LEASH_HELD_EXIT, runAkumaBody, recordPluginDiagnostic, } from "./akuma/body.js";
4
4
  import { worldRootForAkumaPaths } from "./akuma/identity.js";
5
5
  import { composeRequestCommands } from "./akuma/request-wire.js";
6
6
  import { localExecutionContext } from "./akuma/requests.js";
@@ -108,7 +108,9 @@ export async function externalRequestCommandsFor(launch, processConfiguration) {
108
108
  const world = await World.prove(worldRootForAkumaPaths(launch.paths));
109
109
  return {
110
110
  world,
111
- commands: composeRequestCommands(selectionRequestCommands(selectionRequestPort(world)), contractRequestCommands(contractUpstream(processConfiguration)), taskMutationRequestCommands(taskMutationRequestPort())),
111
+ commands: composeRequestCommands(selectionRequestCommands(selectionRequestPort(world, (message) => {
112
+ void recordPluginDiagnostic(launch.paths, "outcome observation", message);
113
+ })), contractRequestCommands(contractUpstream(processConfiguration)), taskMutationRequestCommands(taskMutationRequestPort())),
112
114
  };
113
115
  }
114
116
  function bodyProcessConfiguration() {
@@ -2,7 +2,7 @@ import { CliUsageError, isBlankInput } from "../usage.js";
2
2
  import { readFile } from "node:fs/promises";
3
3
  import { resolve } from "node:path";
4
4
  import { squareAssignedParticipantName } from "@astrosheep/square";
5
- import { emitInitiatingPluginSignal } from "../../plugin/akuma-signals.js";
5
+ import { emitPluginSignal } from "../../plugin/akuma-signals.js";
6
6
  import { canonicalBirthCwd } from "../../akuma/call-input.js";
7
7
  import { Schema } from "../../akuma/index.js";
8
8
  import { executionChannel } from "../../akuma/requests.js";
@@ -73,17 +73,11 @@ async function inputInitiator(input) {
73
73
  // The initiator still travels with the request either way.
74
74
  if (!(await World.established(input.path)))
75
75
  return { initiator };
76
- try {
77
- await emitInitiatingPluginSignal({
78
- world: input.path,
79
- ...(input.settings === undefined ? {} : { settings: input.settings }),
80
- initiator,
81
- reportDiagnostic: (message) => writeStderr(message),
82
- });
83
- }
84
- catch (error) {
85
- writeStderr(`! plugin initiation: ${error instanceof Error ? error.message : String(error)}`);
86
- }
76
+ await emitPluginSignal({
77
+ world: input.path,
78
+ ...(input.settings === undefined ? {} : { settings: input.settings }),
79
+ reportDiagnostic: (message) => writeStderr(message),
80
+ }, { kind: "akuma.initiating", initiator });
87
81
  return { initiator };
88
82
  }
89
83
  function emitAnswer(body, exact) {
@@ -47,8 +47,8 @@ export function renderInstallHelp() {
47
47
  return `${INSTALL_ROOT_PURPOSE}\n\n${usageLine(INSTALL_USAGE)}\n\n--all continues past failures; any failure exits 1.`;
48
48
  }
49
49
  const INSTALL_TIMEOUT_MS = 5 * 60 * 1000;
50
- const ASSET_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "../../../integrations/marketplace");
51
- const PLUGIN_ROOT = resolve(ASSET_ROOT, "plugins/keiyaku");
50
+ const ASSET_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "../../../plugins");
51
+ const OPENCODE_PLUGIN_ROOT = resolve(ASSET_ROOT, "opencode");
52
52
  const PI_PACKAGE_SOURCE = "npm:@astrosheep/keiyaku";
53
53
  function recipes(harness) {
54
54
  switch (harness) {
@@ -64,7 +64,7 @@ function recipes(harness) {
64
64
  ["claude", "plugin", "update", "keiyaku@keiyaku", "--scope", "user"],
65
65
  ];
66
66
  case "opencode":
67
- return [["opencode", "plugin", PLUGIN_ROOT, "--global", "--force"]];
67
+ return [["opencode", "plugin", OPENCODE_PLUGIN_ROOT, "--global", "--force"]];
68
68
  case "pi":
69
69
  return [["pi", "install", PI_PACKAGE_SOURCE]];
70
70
  }
@@ -13,7 +13,7 @@ import { moveAlias } from "../alias/index.js";
13
13
  import { AuthorityCorruptionError } from "../core/facts/errors.js";
14
14
  import { publishDispatch, readDispatch } from "../dispatch/index.js";
15
15
  import { parseAkumaAlias } from "../identity/selector.js";
16
- import { emitCalledPluginSignal } from "../plugin/akuma-signals.js";
16
+ import { emitPluginSignal, outcomePluginObserver } from "../plugin/akuma-signals.js";
17
17
  import { World } from "../world.js";
18
18
  import { addressAkuma } from "./address.js";
19
19
  import { requireInput } from "./input.js";
@@ -108,9 +108,9 @@ function pluginDiagnostic(error) {
108
108
  const message = error instanceof Error ? error.message : String(error);
109
109
  return message.length <= 500 ? message : `${message.slice(0, 500)}...`;
110
110
  }
111
- async function recordPluginDiagnostic(paths, error) {
111
+ async function recordPluginDiagnostic(paths, error, occurrence = "call admission") {
112
112
  try {
113
- await appendFile(paths.log, `plugin call admission failed: ${pluginDiagnostic(error)}\n`);
113
+ await appendFile(paths.log, `plugin ${occurrence} failed: ${pluginDiagnostic(error)}\n`);
114
114
  }
115
115
  catch {
116
116
  // Plugin diagnostic loss must not change an already admitted call.
@@ -126,22 +126,16 @@ async function emitCalledSignal(input) {
126
126
  try {
127
127
  const paths = pathsForAkuId(input.path, input.admitted.id);
128
128
  const callerAkumaId = input.admitted.requested ? await requestedParent(input.path, input.admitted.id) : undefined;
129
- const reportDiagnostic = (message) => {
130
- void recordPluginDiagnostic(paths, message);
131
- };
132
- try {
133
- await emitCalledPluginSignal({
134
- world: input.path,
135
- ...(input.settings === undefined ? {} : { settings: input.settings }),
136
- reportDiagnostic,
137
- akumaId: input.admitted.id,
138
- ...(callerAkumaId === undefined ? {} : { callerAkumaId }),
139
- ...(input.contractId === undefined ? {} : { contractId: input.contractId }),
140
- });
141
- }
142
- catch (error) {
143
- await recordPluginDiagnostic(paths, error);
144
- }
129
+ await emitPluginSignal({
130
+ world: input.path,
131
+ ...(input.settings === undefined ? {} : { settings: input.settings }),
132
+ reportDiagnostic: (message) => void recordPluginDiagnostic(paths, message),
133
+ }, {
134
+ kind: "akuma.called",
135
+ akumaId: input.admitted.id,
136
+ ...(callerAkumaId === undefined ? {} : { callerAkumaId }),
137
+ ...(input.contractId === undefined ? {} : { contractId: input.contractId }),
138
+ });
145
139
  }
146
140
  catch {
147
141
  // Plugin delivery must not change an already admitted call.
@@ -320,6 +314,13 @@ async function observeCall(input) {
320
314
  tellId: input.initialTell.tellId,
321
315
  timeoutMs: input.timeoutMs,
322
316
  receipt: input.receipt,
317
+ outcomeObserved: outcomePluginObserver({
318
+ world: input.path,
319
+ ...(input.settings === undefined ? {} : { settings: input.settings }),
320
+ reportDiagnostic: (message) => {
321
+ void recordPluginDiagnostic(pathsForAkuId(input.path, input.akuma), message, "outcome observation");
322
+ },
323
+ }),
323
324
  ...(input.signal === undefined ? {} : { signal: input.signal }),
324
325
  onObserve,
325
326
  });
@@ -400,6 +401,7 @@ export async function callAkumas(input, execution = localExecutionContext()) {
400
401
  initialTell: parsed.initialTell,
401
402
  receipt,
402
403
  timeoutMs: parsed.timeoutMs,
404
+ ...(parsed.settings === undefined ? {} : { settings: parsed.settings }),
403
405
  ...(parsed.signal === undefined ? {} : { signal: parsed.signal }),
404
406
  ...(parsed.observe === undefined ? {} : { observe: parsed.observe }),
405
407
  });
@@ -13,6 +13,7 @@ import { observeDispatchAssociations } from "../dispatch/index.js";
13
13
  import { observeContractAt } from "../git/observe.js";
14
14
  import { withGitDecodeChannel } from "../git/read-observation.js";
15
15
  import { observeCreatedTaskObservations } from "../task/created-observation.js";
16
+ import { outcomePluginObserver } from "../plugin/akuma-signals.js";
16
17
  import { addressAkuma, addressAkumaSet, resolveAkuma, resolveAkumaSet, } from "./address.js";
17
18
  import { requireInput } from "./input.js";
18
19
  import { scopeForRepo } from "./repo.js";
@@ -77,6 +78,7 @@ function localSelection() {
77
78
  const result = await executeAskAkuma({
78
79
  path: addressed.path,
79
80
  id: addressed.id,
81
+ outcomeObserved: outcomePluginObserver({ world: addressed.path, reportDiagnostic: console.error }),
80
82
  body: input.body,
81
83
  ...(input.timeoutMs === undefined ? {} : { timeoutMs: input.timeoutMs }),
82
84
  ...(input.schema === undefined ? {} : { schemaJson: schemaJsonText(input.schema) }),
@@ -239,7 +241,7 @@ function setAddress(values) {
239
241
  export async function statusAkuma(input) {
240
242
  const addressed = await addressAkuma(input);
241
243
  try {
242
- return await observeAkuma(await new AkumaOwner(addressed.id, addressed.path).status(), addressed.path, input.repo);
244
+ return await observeAkuma(await new AkumaOwner(addressed.id, addressed.path, outcomePluginObserver({ world: addressed.path, reportDiagnostic: console.error })).status(), addressed.path, input.repo);
243
245
  }
244
246
  catch (error) {
245
247
  throw observationError(addressed.id, error);
@@ -268,6 +270,7 @@ async function localWait(addressed, input) {
268
270
  path: addressed.path,
269
271
  ids: addressed.orderedIds,
270
272
  completion: input.completion,
273
+ outcomeObserved: outcomePluginObserver({ world: addressed.path, reportDiagnostic: console.error }),
271
274
  ...(input.timeoutMs === undefined ? {} : { timeoutMs: input.timeoutMs }),
272
275
  ...(input.signal === undefined ? {} : { signal: input.signal }),
273
276
  identity: waitIdentityFacts(addressed.path, input.repo, addressed.orderedIds),
@@ -390,7 +393,7 @@ export async function historyAkuma(input) {
390
393
  validateHistoryInput(values);
391
394
  const addressed = await addressAkuma(directAddress(values));
392
395
  try {
393
- const handle = new AkumaOwner(addressed.id, addressed.path);
396
+ const handle = new AkumaOwner(addressed.id, addressed.path, outcomePluginObserver({ world: addressed.path, reportDiagnostic: console.error }));
394
397
  const contract = (await dispatchAssociations(values.repo, [addressed.id])).get(addressed.id);
395
398
  if (values.last === true) {
396
399
  const answer = await handle.lastAnswer();
@@ -1,18 +1,17 @@
1
1
  import type { Settings } from "../settings.js";
2
2
  import type { WorldRoot } from "../world.js";
3
- type CalledPluginSignalInput = Readonly<{
3
+ import type { PluginSignal } from "./public.js";
4
+ type PluginSignalEmission = Readonly<{
4
5
  world: WorldRoot;
5
6
  settings?: Settings;
6
7
  reportDiagnostic: (message: string) => void;
7
- akumaId: string;
8
- callerAkumaId?: string;
9
- contractId?: string;
10
8
  }>;
11
- export declare function emitInitiatingPluginSignal(input: Readonly<{
12
- world: WorldRoot;
13
- settings?: Settings;
14
- initiator: string;
15
- reportDiagnostic: (message: string) => void;
16
- }>): Promise<void>;
17
- export declare function emitCalledPluginSignal(input: CalledPluginSignalInput): Promise<void>;
9
+ /**
10
+ * The one producer entry for plugin signals. It owns runtime acquisition, the
11
+ * World Settings fallback, bounded delivery, and failure isolation: a delivery
12
+ * problem reaches the producer only as a diagnostic, never as a thrown error.
13
+ */
14
+ export declare function emitPluginSignal(emission: PluginSignalEmission, signal: PluginSignal): Promise<void>;
15
+ /** One public observer operation, never shared with incidental owner reads. */
16
+ export declare function outcomePluginObserver(emission: PluginSignalEmission, observer?: string): (akumaId: string, historyIds: readonly string[]) => Promise<void>;
18
17
  export {};
@@ -1,22 +1,38 @@
1
- import { pluginRuntime } from "./runtime.js";
2
- export async function emitInitiatingPluginSignal(input) {
3
- const runtime = await pluginRuntime({
4
- world: input.world,
5
- ...(input.settings === undefined ? {} : { settings: input.settings }),
6
- reportDiagnostic: input.reportDiagnostic,
7
- });
8
- await runtime.emit({ kind: "akuma.initiating", initiator: input.initiator }, input.reportDiagnostic);
1
+ import { pluginRuntime, reportPluginDiagnostic } from "./runtime.js";
2
+ /**
3
+ * The one producer entry for plugin signals. It owns runtime acquisition, the
4
+ * World Settings fallback, bounded delivery, and failure isolation: a delivery
5
+ * problem reaches the producer only as a diagnostic, never as a thrown error.
6
+ */
7
+ export async function emitPluginSignal(emission, signal) {
8
+ try {
9
+ const runtime = await pluginRuntime({
10
+ world: emission.world,
11
+ ...(emission.settings === undefined ? {} : { settings: emission.settings }),
12
+ reportDiagnostic: emission.reportDiagnostic,
13
+ });
14
+ await runtime.emit(signal, emission.reportDiagnostic);
15
+ }
16
+ catch (error) {
17
+ reportPluginDiagnostic(emission.reportDiagnostic, "runtime", "signal", error);
18
+ }
9
19
  }
10
- export async function emitCalledPluginSignal(input) {
11
- const runtime = await pluginRuntime({
12
- world: input.world,
13
- ...(input.settings === undefined ? {} : { settings: input.settings }),
14
- reportDiagnostic: input.reportDiagnostic,
15
- });
16
- await runtime.emit({
17
- kind: "akuma.called",
18
- akumaId: input.akumaId,
19
- ...(input.callerAkumaId === undefined ? {} : { callerAkumaId: input.callerAkumaId }),
20
- ...(input.contractId === undefined ? {} : { contractId: input.contractId }),
21
- }, input.reportDiagnostic);
20
+ /** One public observer operation, never shared with incidental owner reads. */
21
+ export function outcomePluginObserver(emission, observer) {
22
+ const seen = new Set();
23
+ return async (akumaId, historyIds) => {
24
+ const fresh = historyIds.filter((historyId) => {
25
+ const key = `${akumaId}:${historyId}`;
26
+ if (seen.has(key))
27
+ return false;
28
+ seen.add(key);
29
+ return true;
30
+ });
31
+ await Promise.all(fresh.map(async (historyId) => await emitPluginSignal(emission, {
32
+ kind: "akuma.outcome-observed",
33
+ akumaId,
34
+ historyId,
35
+ ...(observer === undefined ? {} : { observer }),
36
+ })));
37
+ };
22
38
  }
@@ -24,6 +24,11 @@ export type PluginSignalMap = Readonly<{
24
24
  callerAkumaId?: string;
25
25
  contractId?: string;
26
26
  }>;
27
+ "akuma.outcome-observed": Readonly<{
28
+ akumaId: string;
29
+ historyId: string;
30
+ observer?: string;
31
+ }>;
27
32
  "akuma.turn-outcome": Readonly<{
28
33
  akumaId: string;
29
34
  bodySequence: number;
@@ -10,5 +10,6 @@ type PluginRuntimeInput = Readonly<{
10
10
  export type PluginRuntime = Readonly<{
11
11
  emit(signal: PluginSignal, reportDiagnostic?: PluginDiagnostic): Promise<void>;
12
12
  }>;
13
+ export declare function reportPluginDiagnostic(report: PluginDiagnostic | undefined, subject: string, stage: string, error: unknown): void;
13
14
  export declare function pluginRuntime(input: PluginRuntimeInput): Promise<PluginRuntime>;
14
15
  export {};