@primitive.ai/prim 0.1.0-alpha.49 → 0.1.0-alpha.51

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.
@@ -4,7 +4,7 @@
4
4
  import { existsSync as existsSync6, readFileSync as readFileSync5, unlinkSync as unlinkSync3 } from "fs";
5
5
  import { createServer } from "net";
6
6
  import { homedir as homedir5 } from "os";
7
- import { join as join7 } from "path";
7
+ import { join as join8 } from "path";
8
8
  import { fileURLToPath } from "url";
9
9
 
10
10
  // src/client.ts
@@ -163,6 +163,7 @@ var CONFIG_DIR_MODE = 448;
163
163
  var CREDENTIAL_FILE_MODE = 384;
164
164
  var REFRESH_THRESHOLD_MS = 6e4;
165
165
  var DEFAULT_API_URL = "https://api.getprimitive.ai";
166
+ var AUTH_EXPIRED_MESSAGE = "Authentication expired. Run `prim auth login` to re-authenticate.";
166
167
  function loadEnvFile() {
167
168
  const envVars = {};
168
169
  for (const file of [".env.local", ".env"]) {
@@ -319,6 +320,7 @@ async function performTokenRefresh(options = {}) {
319
320
  if (selected?.source !== "token_file") return void 0;
320
321
  const startingGeneration = readTrimmed(REFRESH_TOKEN_PATH);
321
322
  if (!startingGeneration || isSessionEnded()) return void 0;
323
+ const startingAccessToken = selected.token;
322
324
  return withCredentialLock(
323
325
  async () => {
324
326
  const currentCredential = resolveAuthCredential();
@@ -328,6 +330,9 @@ async function performTokenRefresh(options = {}) {
328
330
  if (currentGeneration !== startingGeneration) {
329
331
  return currentCredential.token;
330
332
  }
333
+ if (currentCredential.token !== startingAccessToken) {
334
+ return currentCredential.token;
335
+ }
331
336
  if (isSessionEnded()) return void 0;
332
337
  const response = await fetch(`${getSiteUrl()}/mcp/broker/refresh`, {
333
338
  method: "POST",
@@ -412,6 +417,12 @@ async function request(method, path, body, options) {
412
417
  });
413
418
  if (token) credential = { token, source: "token_file" };
414
419
  }
420
+ if (!credential) {
421
+ throw new HttpError(401, AUTH_EXPIRED_MESSAGE);
422
+ }
423
+ if (isTokenExpiringSoon(credential) && isSessionEnded()) {
424
+ throw new HttpError(401, AUTH_EXPIRED_MESSAGE);
425
+ }
415
426
  const doFetch = (token) => {
416
427
  const headers = { "Content-Type": "application/json" };
417
428
  if (token) headers.Authorization = `Bearer ${token}`;
@@ -441,7 +452,7 @@ async function request(method, path, body, options) {
441
452
  }
442
453
  if (!response.ok) {
443
454
  if (response.status === 401) {
444
- throw new HttpError(401, "Authentication expired. Run `prim auth login` to re-authenticate.");
455
+ throw new HttpError(401, AUTH_EXPIRED_MESSAGE);
445
456
  }
446
457
  const errorBody = await response.json().catch(() => null);
447
458
  throw new HttpError(response.status, errorBody?.error ?? `HTTP ${response.status}`);
@@ -457,6 +468,7 @@ function getClient() {
457
468
 
458
469
  // src/flusher.ts
459
470
  import { createReadStream, renameSync as renameSync2, unlinkSync } from "fs";
471
+ import { dirname as dirname4, join as join4 } from "path";
460
472
  import { createInterface } from "readline";
461
473
 
462
474
  // src/ingest-response.ts
@@ -824,11 +836,23 @@ async function flushOnce() {
824
836
  return { flushed: total };
825
837
  }
826
838
  var flushInFlight;
839
+ var FLUSH_LOCK_PATH = join4(dirname4(JOURNAL_DIR), ".flush.lock");
840
+ var FLUSH_LOCK_TIMEOUT_MS = 250;
841
+ function isFlushLockContended(error) {
842
+ return error instanceof Error && error.message.startsWith("timed out waiting for file lock");
843
+ }
827
844
  function flush() {
828
845
  if (flushInFlight) {
829
846
  return flushInFlight;
830
847
  }
831
- const attempt = flushOnce().finally(() => {
848
+ const attempt = withFileLock(FLUSH_LOCK_PATH, flushOnce, {
849
+ timeoutMs: FLUSH_LOCK_TIMEOUT_MS
850
+ }).catch((error) => {
851
+ if (isFlushLockContended(error)) {
852
+ return { flushed: 0, skipped: true };
853
+ }
854
+ throw error;
855
+ }).finally(() => {
832
856
  if (flushInFlight === attempt) {
833
857
  flushInFlight = void 0;
834
858
  }
@@ -840,8 +864,8 @@ function flush() {
840
864
  // src/daemon/client.ts
841
865
  import { createConnection } from "net";
842
866
  import { homedir as homedir3 } from "os";
843
- import { join as join4 } from "path";
844
- var SOCK_PATH = join4(homedir3(), ".config", "prim", "sock");
867
+ import { join as join5 } from "path";
868
+ var SOCK_PATH = join5(homedir3(), ".config", "prim", "sock");
845
869
  var DEFAULT_TIMEOUT_MS2 = 250;
846
870
  var nextRequestId = 1;
847
871
  function daemonRequest(method, params = {}, opts = {}) {
@@ -936,7 +960,7 @@ function assertCallerEnvMatches(callerEnv, boundEnv) {
936
960
  // src/daemon/health.ts
937
961
  import { chmodSync as chmodSync2, existsSync as existsSync4, mkdirSync as mkdirSync4, renameSync as renameSync3, writeFileSync as writeFileSync3 } from "fs";
938
962
  import { homedir as homedir4 } from "os";
939
- import { dirname as dirname4, join as join5 } from "path";
963
+ import { dirname as dirname5, join as join6 } from "path";
940
964
  var CONFIG_DIR_MODE2 = 448;
941
965
  var HEALTH_FILE_MODE = 384;
942
966
  var INGESTION_SLA_MS = 3e4;
@@ -944,7 +968,7 @@ var HEARTBEAT_FRESH_MS = 9e4;
944
968
  var HEARTBEAT_RETRY_CAP_MS = 3e4;
945
969
  var INGESTION_RETRY_BASE_MS = 5e3;
946
970
  var INGESTION_RETRY_CAP_MS = 15e3;
947
- var DAEMON_HEALTH_PATH = join5(homedir4(), ".config", "prim", "daemon-health.json");
971
+ var DAEMON_HEALTH_PATH = join6(homedir4(), ".config", "prim", "daemon-health.json");
948
972
  function createDaemonHealthState(version, pid, startedAt2) {
949
973
  return {
950
974
  schemaVersion: 1,
@@ -982,11 +1006,11 @@ function ingestionRetryDelayMs(consecutiveFailures, random = Math.random) {
982
1006
  return retryDelayMs(consecutiveFailures, INGESTION_RETRY_CAP_MS, random);
983
1007
  }
984
1008
  function writeDaemonHealthState(state, path = DAEMON_HEALTH_PATH) {
985
- const dir = dirname4(path);
1009
+ const dir = dirname5(path);
986
1010
  if (!existsSync4(dir)) {
987
1011
  mkdirSync4(dir, { recursive: true, mode: CONFIG_DIR_MODE2 });
988
1012
  }
989
- const tmp = join5(dir, `.${process.pid}.daemon-health.tmp`);
1013
+ const tmp = join6(dir, `.${process.pid}.daemon-health.tmp`);
990
1014
  writeFileSync3(tmp, `${JSON.stringify(state, null, 2)}
991
1015
  `, { mode: HEALTH_FILE_MODE });
992
1016
  chmodSync2(tmp, HEALTH_FILE_MODE);
@@ -1006,7 +1030,7 @@ import {
1006
1030
  unlinkSync as unlinkSync2,
1007
1031
  writeFileSync as writeFileSync4
1008
1032
  } from "fs";
1009
- import { join as join6 } from "path";
1033
+ import { join as join7 } from "path";
1010
1034
  var DIR_MODE = 448;
1011
1035
  var FILE_MODE2 = 384;
1012
1036
  var DAEMON_STARTUP_GRACE_MS = 5e3;
@@ -1037,10 +1061,10 @@ function daemonProcessIsAlive(pid) {
1037
1061
  }
1038
1062
  }
1039
1063
  function readDaemonOwner(configDir) {
1040
- return readLockRecord(join6(configDir, "daemon.lock", "owner.json"));
1064
+ return readLockRecord(join7(configDir, "daemon.lock", "owner.json"));
1041
1065
  }
1042
1066
  function readDaemonPid(configDir) {
1043
- const path = join6(configDir, "daemon.pid");
1067
+ const path = join7(configDir, "daemon.pid");
1044
1068
  const pid = numericPid(path);
1045
1069
  if (pid === void 0) {
1046
1070
  return;
@@ -1055,9 +1079,9 @@ function acquireDaemonOwnership(configDir, opts = {}) {
1055
1079
  const pid = opts.pid ?? process.pid;
1056
1080
  const now = opts.now ?? Date.now();
1057
1081
  const isAlive = opts.isAlive ?? daemonProcessIsAlive;
1058
- const lockDir = join6(configDir, "daemon.lock");
1059
- const ownerPath2 = join6(lockDir, "owner.json");
1060
- const pidPath = join6(configDir, "daemon.pid");
1082
+ const lockDir = join7(configDir, "daemon.lock");
1083
+ const ownerPath2 = join7(lockDir, "owner.json");
1084
+ const pidPath = join7(configDir, "daemon.pid");
1061
1085
  const instanceId = randomUUID();
1062
1086
  if (!existsSync5(configDir)) {
1063
1087
  mkdirSync5(configDir, { recursive: true, mode: DIR_MODE });
@@ -1155,8 +1179,8 @@ function releaseDaemonOwnership(ownership2, socketPath) {
1155
1179
  }
1156
1180
 
1157
1181
  // src/daemon/server.ts
1158
- var CONFIG_DIR = join7(homedir5(), ".config", "prim");
1159
- var SOCK_PATH2 = join7(CONFIG_DIR, "sock");
1182
+ var CONFIG_DIR = join8(homedir5(), ".config", "prim");
1183
+ var SOCK_PATH2 = join8(CONFIG_DIR, "sock");
1160
1184
  var HEARTBEAT_INTERVAL_MS = 3e4;
1161
1185
  var INGESTION_POLL_INTERVAL_MS = 15e3;
1162
1186
  var TOKEN_CHECK_INTERVAL_MS = 6e4;
@@ -1190,7 +1214,7 @@ function resolveRuntimeVersion() {
1190
1214
  }
1191
1215
  try {
1192
1216
  const here = fileURLToPath(new URL(".", import.meta.url));
1193
- const pkg = JSON.parse(readFileSync5(join7(here, "..", "..", "package.json"), "utf-8"));
1217
+ const pkg = JSON.parse(readFileSync5(join8(here, "..", "..", "package.json"), "utf-8"));
1194
1218
  return pkg.version ?? "unknown";
1195
1219
  } catch {
1196
1220
  return "unknown";
@@ -1430,6 +1454,10 @@ async function runIngestionLoop() {
1430
1454
  persistHealth();
1431
1455
  try {
1432
1456
  const result = await flush();
1457
+ if (result.skipped) {
1458
+ scheduleIngestion(INGESTION_POLL_INTERVAL_MS);
1459
+ return;
1460
+ }
1433
1461
  daemonHealth.ingestion.lastSuccessAt = Date.now();
1434
1462
  daemonHealth.ingestion.lastAcknowledgedCount = result.flushed;
1435
1463
  daemonHealth.ingestion.consecutiveFailures = 0;
@@ -5,11 +5,11 @@ import {
5
5
  import {
6
6
  appendMove,
7
7
  resolveOrg
8
- } from "../chunk-5K725K35.js";
8
+ } from "../chunk-BOTKPLTI.js";
9
9
  import {
10
10
  isRepoActiveForCapture
11
11
  } from "../chunk-R5ZJRSLV.js";
12
- import "../chunk-TPROV45L.js";
12
+ import "../chunk-DWDEQRWN.js";
13
13
 
14
14
  // src/hooks/post-commit.ts
15
15
  import { execSync, spawn } from "child_process";
@@ -13,7 +13,7 @@ import {
13
13
  import {
14
14
  appendMove,
15
15
  resolveOrg
16
- } from "../chunk-5K725K35.js";
16
+ } from "../chunk-BOTKPLTI.js";
17
17
  import {
18
18
  getOrCreateWorkspaceId
19
19
  } from "../chunk-IMAIBPUC.js";
@@ -25,7 +25,7 @@ import {
25
25
  } from "../chunk-R5ZJRSLV.js";
26
26
  import {
27
27
  getClient
28
- } from "../chunk-TPROV45L.js";
28
+ } from "../chunk-DWDEQRWN.js";
29
29
  import {
30
30
  normalizeEnvelope,
31
31
  parseAgent
@@ -2,8 +2,8 @@
2
2
  import {
3
3
  checkAffectedDecisions,
4
4
  formatDecisionsWarning
5
- } from "../chunk-WRPKAPVE.js";
6
- import "../chunk-TPROV45L.js";
5
+ } from "../chunk-VDLAAUWV.js";
6
+ import "../chunk-DWDEQRWN.js";
7
7
  import "../chunk-UTKQTZHL.js";
8
8
 
9
9
  // src/hooks/pre-commit.ts
@@ -8,7 +8,7 @@ import {
8
8
  import {
9
9
  getClient,
10
10
  getSiteUrl
11
- } from "../chunk-TPROV45L.js";
11
+ } from "../chunk-DWDEQRWN.js";
12
12
  import {
13
13
  daemonRequest
14
14
  } from "../chunk-UTKQTZHL.js";
@@ -9,17 +9,17 @@ import {
9
9
  import {
10
10
  appendMove,
11
11
  resolveOrg
12
- } from "../chunk-5K725K35.js";
12
+ } from "../chunk-BOTKPLTI.js";
13
13
  import {
14
14
  buildHookOutput,
15
15
  handoffHookOutput
16
- } from "../chunk-S5NUHFAD.js";
16
+ } from "../chunk-4GLVJLEQ.js";
17
17
  import {
18
18
  FEEDBACK_DEADLINE_MS,
19
19
  acknowledgeDecisionFeedback,
20
20
  leaseDecisionFeedback,
21
21
  renderFeedback
22
- } from "../chunk-CK75QBCB.js";
22
+ } from "../chunk-WELBNODJ.js";
23
23
  import {
24
24
  getOrCreateWorkspaceId
25
25
  } from "../chunk-IMAIBPUC.js";
@@ -29,7 +29,7 @@ import {
29
29
  import {
30
30
  isRepoActiveForCapture
31
31
  } from "../chunk-R5ZJRSLV.js";
32
- import "../chunk-TPROV45L.js";
32
+ import "../chunk-DWDEQRWN.js";
33
33
  import {
34
34
  normalizeEnvelope,
35
35
  parseAgent
@@ -1,14 +1,17 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ refreshClaudePlugins
4
+ } from "../chunk-UJEQX52M.js";
2
5
  import {
3
6
  buildHookOutput,
4
7
  handoffHookOutput
5
- } from "../chunk-S5NUHFAD.js";
8
+ } from "../chunk-4GLVJLEQ.js";
6
9
  import {
7
10
  FEEDBACK_DEADLINE_MS,
8
11
  acknowledgeDecisionFeedback,
9
12
  leaseDecisionFeedback,
10
13
  renderFeedback
11
- } from "../chunk-CK75QBCB.js";
14
+ } from "../chunk-WELBNODJ.js";
12
15
  import {
13
16
  getOrCreateWorkspaceId
14
17
  } from "../chunk-IMAIBPUC.js";
@@ -17,11 +20,13 @@ import {
17
20
  warmBinCache
18
21
  } from "../chunk-HSZPTVKU.js";
19
22
  import {
23
+ gitToplevel,
20
24
  isRepoActiveForCapture
21
25
  } from "../chunk-R5ZJRSLV.js";
22
26
  import {
23
- getSiteUrl
24
- } from "../chunk-TPROV45L.js";
27
+ getSiteUrl,
28
+ isSessionEnded
29
+ } from "../chunk-DWDEQRWN.js";
25
30
  import {
26
31
  daemonRequest
27
32
  } from "../chunk-UTKQTZHL.js";
@@ -50,59 +55,33 @@ function kickDaemonEnsure(options = {}) {
50
55
  }
51
56
  }
52
57
 
53
- // src/hooks/session-start.ts
54
- var STDIN_TIMEOUT_MS = 1e3;
55
- var DAEMON_TIMEOUT_MS = 250;
56
- var outputAttempted = false;
57
- function emitOutput(output, acknowledge) {
58
- outputAttempted = true;
59
- return handoffHookOutput(output, acknowledge);
58
+ // src/hooks/reauth-notice.ts
59
+ var REAUTH_NOTICE = "[prim] authentication ended \u2014 run `prim auth login` to resume decision capture";
60
+ function reauthNoticeFields(agent) {
61
+ if (agent === "codex") return { additionalContext: REAUTH_NOTICE };
62
+ if (agent === "claude_code") return { systemMessage: REAUTH_NOTICE };
63
+ return;
60
64
  }
61
- function readStdin() {
62
- return new Promise((resolve, reject) => {
63
- const chunks = [];
64
- const timer = setTimeout(() => {
65
- reject(new Error("stdin read timeout"));
66
- }, STDIN_TIMEOUT_MS);
67
- process.stdin.on("data", (chunk) => chunks.push(chunk));
68
- process.stdin.on("end", () => {
69
- clearTimeout(timer);
70
- resolve(Buffer.concat(chunks).toString("utf-8"));
71
- });
72
- process.stdin.on("error", (err) => {
73
- clearTimeout(timer);
74
- reject(err);
75
- });
76
- });
77
- }
78
- async function main() {
79
- const feedbackSignal = AbortSignal.timeout(FEEDBACK_DEADLINE_MS);
80
- warmBinCache();
81
- const agent = parseAgent(process.argv);
82
- let raw;
83
- try {
84
- raw = await readStdin();
85
- } catch {
86
- await emitOutput(buildHookOutput({}));
87
- return;
88
- }
65
+
66
+ // src/hooks/session-start-core.ts
67
+ var DAEMON_TIMEOUT_MS = 250;
68
+ var PRIM_SKILL_REMINDER = "Primitive is active in this repository. When this task chooses between plausible approaches or establishes or changes a lasting goal, priority, constraint, invariant, default, commitment, tradeoff, exception, or shared instruction, invoke the `prim` skill before finishing. Never invoke `prim` for routine implementation that merely follows an existing decision made before this task or for temporary tactics; they never qualify, including for evaluation. When a direct request replaces one lasting default with another but supplies no rationale, complete the work, invoke the skill, and ask one concise rationale question at the task boundary, even if the user requested only implementation or recording fails.";
69
+ async function processSessionStart(raw, agent, feedbackSignal = AbortSignal.timeout(FEEDBACK_DEADLINE_MS)) {
89
70
  let envelope;
90
71
  try {
91
- envelope = normalizeEnvelope(
92
- JSON.parse(raw),
93
- agent
94
- );
72
+ const parsed = JSON.parse(raw);
73
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
74
+ return { output: buildHookOutput({}) };
75
+ }
76
+ envelope = normalizeEnvelope(parsed, agent);
95
77
  } catch {
96
- await emitOutput(buildHookOutput({}));
97
- return;
78
+ return { output: buildHookOutput({}) };
98
79
  }
99
80
  if (envelope.hook_event_name !== "SessionStart") {
100
- await emitOutput(buildHookOutput({}));
101
- return;
81
+ return { output: buildHookOutput({}) };
102
82
  }
103
83
  if (typeof envelope.session_id !== "string" || envelope.session_id.length === 0) {
104
- await emitOutput(buildHookOutput({}));
105
- return;
84
+ return { output: buildHookOutput({}) };
106
85
  }
107
86
  kickDaemonEnsure();
108
87
  await daemonRequest(
@@ -110,6 +89,28 @@ async function main() {
110
89
  { sessionId: envelope.session_id },
111
90
  { timeoutMs: DAEMON_TIMEOUT_MS }
112
91
  );
92
+ const cwd = envelope.cwd ?? process.cwd();
93
+ let active = false;
94
+ let skillState = { installed: 0, refreshed: 0 };
95
+ if (agent === "claude_code") {
96
+ try {
97
+ const projectRoot = gitToplevel(cwd);
98
+ active = projectRoot !== null && isRepoActiveForCapture(cwd);
99
+ skillState = await refreshClaudePlugins(
100
+ cwd,
101
+ active && projectRoot !== null ? { includeProject: true, projectRoot } : { includeProject: false }
102
+ );
103
+ } catch {
104
+ }
105
+ }
106
+ if (isSessionEnded()) {
107
+ const notice = reauthNoticeFields(agent);
108
+ if (notice) {
109
+ return {
110
+ output: buildHookOutput({ ...notice, reloadSkills: skillState.refreshed > 0 })
111
+ };
112
+ }
113
+ }
113
114
  if (agent === "codex") {
114
115
  const snapshot = await daemonRequest(
115
116
  "status_snapshot",
@@ -119,15 +120,17 @@ async function main() {
119
120
  { timeoutMs: DAEMON_TIMEOUT_MS }
120
121
  );
121
122
  if (snapshot && !snapshot.presenceStale && typeof snapshot.onlineCount === "number") {
122
- await emitOutput(
123
- buildHookOutput({ additionalContext: `[prim] team: ${snapshot.onlineCount} online` })
124
- );
125
- return;
123
+ return {
124
+ output: buildHookOutput({
125
+ additionalContext: `[prim] team: ${snapshot.onlineCount} online`
126
+ })
127
+ };
126
128
  }
127
129
  }
128
130
  if (agent === "claude_code") {
129
- const cwd = envelope.cwd ?? process.cwd();
130
- if (isRepoActiveForCapture(cwd)) {
131
+ const additionalContext = active && skillState.installed > 0 ? PRIM_SKILL_REMINDER : void 0;
132
+ const reloadSkills = skillState.refreshed > 0;
133
+ if (active) {
131
134
  const identity = getOrCreateWorkspaceId(cwd);
132
135
  if (identity.status === "ready") {
133
136
  const lease = await leaseDecisionFeedback({
@@ -136,21 +139,64 @@ async function main() {
136
139
  signal: feedbackSignal
137
140
  });
138
141
  const rendered = lease ? renderFeedback(lease) : void 0;
139
- await emitOutput(
140
- buildHookOutput({ systemMessage: rendered?.systemMessage }),
141
- rendered ? async () => {
142
+ return {
143
+ output: buildHookOutput({
144
+ systemMessage: rendered?.systemMessage,
145
+ additionalContext,
146
+ reloadSkills
147
+ }),
148
+ acknowledge: rendered ? async () => {
142
149
  await acknowledgeDecisionFeedback({
143
150
  workspaceId: identity.workspaceId,
144
151
  deliveries: rendered.deliveries,
145
152
  signal: feedbackSignal
146
153
  });
147
154
  } : void 0
148
- );
149
- return;
155
+ };
150
156
  }
151
157
  }
158
+ return { output: buildHookOutput({ additionalContext, reloadSkills }) };
159
+ }
160
+ return { output: buildHookOutput({}) };
161
+ }
162
+
163
+ // src/hooks/session-start.ts
164
+ var STDIN_TIMEOUT_MS = 1e3;
165
+ var outputAttempted = false;
166
+ function emitOutput(output, acknowledge) {
167
+ outputAttempted = true;
168
+ return handoffHookOutput(output, acknowledge);
169
+ }
170
+ function readStdin() {
171
+ return new Promise((resolve, reject) => {
172
+ const chunks = [];
173
+ const timer = setTimeout(() => {
174
+ reject(new Error("stdin read timeout"));
175
+ }, STDIN_TIMEOUT_MS);
176
+ process.stdin.on("data", (chunk) => chunks.push(chunk));
177
+ process.stdin.on("end", () => {
178
+ clearTimeout(timer);
179
+ resolve(Buffer.concat(chunks).toString("utf-8"));
180
+ });
181
+ process.stdin.on("error", (err) => {
182
+ clearTimeout(timer);
183
+ reject(err);
184
+ });
185
+ });
186
+ }
187
+ async function main() {
188
+ const feedbackSignal = AbortSignal.timeout(FEEDBACK_DEADLINE_MS);
189
+ warmBinCache();
190
+ const agent = parseAgent(process.argv);
191
+ let raw;
192
+ try {
193
+ raw = await readStdin();
194
+ } catch {
195
+ await emitOutput(buildHookOutput({}));
196
+ return;
152
197
  }
153
- await emitOutput(buildHookOutput({}));
198
+ const result = await processSessionStart(raw, agent, feedbackSignal);
199
+ await emitOutput(result.output, result.acknowledge);
154
200
  }
155
201
  void main().catch(async () => {
156
202
  if (!outputAttempted) await emitOutput(buildHookOutput({}));