@rynx-ai/daemon 0.1.11-beta.36 → 0.1.11-beta.38

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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rynx-ai/plugin-channel-lark",
3
- "version": "0.1.11-beta.36",
3
+ "version": "0.1.11-beta.38",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/rynx-ai/rynx.git",
package/dist/db.js CHANGED
@@ -100,6 +100,7 @@ function migrate(conn) {
100
100
  status TEXT NOT NULL,
101
101
  data TEXT NOT NULL,
102
102
  created_by TEXT,
103
+ parent_tool_call_id TEXT,
103
104
  created_at INTEGER NOT NULL,
104
105
  UNIQUE(session_id, position)
105
106
  );
@@ -370,6 +371,7 @@ function migrate(conn) {
370
371
  addColumnIfMissing(conn, "session_message_operations", "response_id", "TEXT");
371
372
  addColumnIfMissing(conn, "session_message_operations", "message_item_id", "TEXT");
372
373
  addColumnIfMissing(conn, "session_message_operations", "execution_snapshot", "TEXT");
374
+ addColumnIfMissing(conn, "session_items", "parent_tool_call_id", "TEXT");
373
375
  addColumnIfMissing(conn, "session_portal_tickets", "grant_handle", "TEXT");
374
376
  addColumnIfMissing(conn, "session_portal_tickets", "embed_origin", "TEXT");
375
377
  addColumnIfMissing(conn, "session_portal_grants", "grant_handle", "TEXT");
@@ -558,6 +558,7 @@ export class PluginHostRpc {
558
558
  return result;
559
559
  }
560
560
  async pumpRun(run, generator) {
561
+ let terminalCompleted = false;
561
562
  let terminalFailure;
562
563
  try {
563
564
  for await (const event of generator) {
@@ -588,6 +589,10 @@ export class PluginHostRpc {
588
589
  terminalFailure = sessionRunFailure(event);
589
590
  if (terminalFailure)
590
591
  break;
592
+ if (sessionRunCompleted(event)) {
593
+ terminalCompleted = true;
594
+ break;
595
+ }
591
596
  }
592
597
  if (!this.disposed &&
593
598
  this.activeRuns.get(run.runId) === run &&
@@ -602,7 +607,7 @@ export class PluginHostRpc {
602
607
  },
603
608
  });
604
609
  }
605
- else {
610
+ else if (terminalCompleted) {
606
611
  await this.emit({
607
612
  event: "session.run.completed",
608
613
  payload: {
@@ -614,6 +619,20 @@ export class PluginHostRpc {
614
619
  },
615
620
  });
616
621
  }
622
+ else {
623
+ await this.emit({
624
+ event: "session.run.failed",
625
+ payload: {
626
+ runId: run.runId,
627
+ sessionId: run.sessionId,
628
+ error: {
629
+ code: "RUNTIME_EVENT_STREAM_ENDED",
630
+ message: "Runtime event stream ended before a terminal response event",
631
+ retryable: true,
632
+ },
633
+ },
634
+ });
635
+ }
617
636
  }
618
637
  }
619
638
  catch (error) {
@@ -1509,6 +1528,13 @@ function sessionEventResponseId(event) {
1509
1528
  }
1510
1529
  function sessionRunFailure(event) {
1511
1530
  const input = jsonRecord(event);
1531
+ if (input?.type === "session.interrupted") {
1532
+ return {
1533
+ code: "RUNTIME_TURN_INTERRUPTED",
1534
+ message: "Runtime Turn was interrupted",
1535
+ retryable: false,
1536
+ };
1537
+ }
1512
1538
  if (input?.type !== "response.failed")
1513
1539
  return undefined;
1514
1540
  const error = jsonRecord(input.error);
@@ -1524,6 +1550,9 @@ function sessionRunFailure(event) {
1524
1550
  retryable: false,
1525
1551
  };
1526
1552
  }
1553
+ function sessionRunCompleted(event) {
1554
+ return jsonRecord(event)?.type === "response.completed";
1555
+ }
1527
1556
  function sessionHostEvent(run, event, delivery) {
1528
1557
  return {
1529
1558
  event: "session.run.event",
@@ -194,10 +194,10 @@ export class SqliteSessionForkStore {
194
194
  }
195
195
  const insertItem = conn.prepare(`INSERT INTO session_items (
196
196
  id, session_id, position, response_id, type, status,
197
- data, created_by, created_at
197
+ data, created_by, parent_tool_call_id, created_at
198
198
  ) VALUES (
199
199
  @id, @session_id, @position, @response_id, @type, @status,
200
- @data, @created_by, @created_at
200
+ @data, @created_by, @parent_tool_call_id, @created_at
201
201
  )`);
202
202
  for (const item of clonedItems) {
203
203
  insertItem.run({
@@ -209,6 +209,7 @@ export class SqliteSessionForkStore {
209
209
  status: item.status,
210
210
  data: JSON.stringify(item.data),
211
211
  created_by: item.createdBy ?? null,
212
+ parent_tool_call_id: item.parentToolCallId ?? null,
212
213
  created_at: item.createdAt,
213
214
  });
214
215
  }
@@ -1,5 +1,6 @@
1
+ import { SessionLogAppendError } from "@rynx-ai/core";
1
2
  import { db } from "./db.js";
2
- import { transaction } from "./sqlite.js";
3
+ import { hasSqlitePrimaryCode, SQLITE_CONSTRAINT, transaction } from "./sqlite.js";
3
4
  function toItem(row) {
4
5
  return {
5
6
  id: row.id,
@@ -9,6 +10,9 @@ function toItem(row) {
9
10
  status: row.status,
10
11
  createdAt: row.created_at,
11
12
  ...(row.created_by ? { createdBy: row.created_by } : {}),
13
+ ...(row.parent_tool_call_id
14
+ ? { parentToolCallId: row.parent_tool_call_id }
15
+ : {}),
12
16
  type: row.type,
13
17
  data: JSON.parse(row.data),
14
18
  };
@@ -19,8 +23,10 @@ export class SqliteSessionLogStore {
19
23
  return [];
20
24
  const conn = db();
21
25
  const insert = conn.prepare(`INSERT INTO session_items
22
- (id, session_id, position, response_id, type, status, data, created_by, created_at)
23
- VALUES (@id, @session_id, @position, @response_id, @type, @status, @data, @created_by, @created_at)`);
26
+ (id, session_id, position, response_id, type, status, data, created_by,
27
+ parent_tool_call_id, created_at)
28
+ VALUES (@id, @session_id, @position, @response_id, @type, @status, @data,
29
+ @created_by, @parent_tool_call_id, @created_at)`);
24
30
  const maxStmt = conn.prepare("SELECT MAX(position) AS max FROM session_items WHERE session_id = ?");
25
31
  const tx = transaction(conn, (batch) => {
26
32
  const { max } = maxStmt.get(sessionId);
@@ -36,6 +42,7 @@ export class SqliteSessionLogStore {
36
42
  status: stored.status,
37
43
  data: JSON.stringify(stored.data),
38
44
  created_by: stored.createdBy ?? null,
45
+ parent_tool_call_id: stored.parentToolCallId ?? null,
39
46
  created_at: stored.createdAt,
40
47
  });
41
48
  const resourceIds = stored.type === "message" && stored.data.role === "user"
@@ -54,10 +61,14 @@ export class SqliteSessionLogStore {
54
61
  const resource = conn.prepare(`SELECT state, message_item_id
55
62
  FROM session_resources
56
63
  WHERE id = ? AND session_id = ?`).get(resourceId, sessionId);
57
- if (!resource ||
58
- resource.state === "uploading" ||
59
- (resource.message_item_id && resource.message_item_id !== stored.id)) {
60
- throw new Error(`Session resource ${resourceId} is not committable`);
64
+ if (!resource) {
65
+ throw new SessionLogAppendError(`Session resource ${resourceId} does not exist`, 422, "session_resource_missing");
66
+ }
67
+ if (resource.state === "uploading") {
68
+ throw new SessionLogAppendError(`Session resource ${resourceId} is still uploading`, 409, "session_resource_not_ready");
69
+ }
70
+ if (resource.message_item_id && resource.message_item_id !== stored.id) {
71
+ throw new SessionLogAppendError(`Session resource ${resourceId} is already committed to another item`, 422, "session_resource_already_committed");
61
72
  }
62
73
  conn.prepare(`UPDATE session_resources
63
74
  SET state = 'committed', message_item_id = ?,
@@ -68,7 +79,17 @@ export class SqliteSessionLogStore {
68
79
  return stored;
69
80
  });
70
81
  });
71
- return tx(items);
82
+ try {
83
+ return tx(items);
84
+ }
85
+ catch (error) {
86
+ if (error instanceof SessionLogAppendError)
87
+ throw error;
88
+ if (hasSqlitePrimaryCode(error, SQLITE_CONSTRAINT)) {
89
+ throw new SessionLogAppendError(error instanceof Error ? error.message : "Session log constraint rejected the item", 422, "session_log_constraint", { cause: error });
90
+ }
91
+ throw error;
92
+ }
72
93
  }
73
94
  async list(sessionId, opts = {}) {
74
95
  const conn = db();
@@ -1008,7 +1008,7 @@ function invalid(message) {
1008
1008
  return new MachineSessionServiceFailure("failed_precondition", message);
1009
1009
  }
1010
1010
  function unavailable(message) {
1011
- return new MachineSessionServiceFailure("failed_precondition", message);
1011
+ return new MachineSessionServiceFailure("failed_precondition", message, undefined, undefined, true);
1012
1012
  }
1013
1013
  function conflict(message) {
1014
1014
  return new MachineSessionServiceFailure("conflict", message);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rynx-ai/daemon",
3
- "version": "0.1.11-beta.36",
3
+ "version": "0.1.11-beta.38",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/rynx-ai/rynx.git",
@@ -51,17 +51,17 @@
51
51
  "tar": "^7.5.19",
52
52
  "undici": "^7.28.0",
53
53
  "ws": "^8.21.0",
54
- "@rynx-ai/core": "0.1.11-beta.36",
55
- "@rynx-ai/emulator": "0.1.11-beta.36",
56
- "@rynx-ai/plugin-sdk": "0.1.11-beta.36",
57
- "@rynx-ai/plugin-runner": "0.1.11-beta.36",
58
- "@rynx-ai/protocol": "0.1.11-beta.36",
59
- "@rynx-ai/remote-runtime-client": "0.1.11-beta.36",
60
- "@rynx-ai/server": "0.1.11-beta.36"
54
+ "@rynx-ai/core": "0.1.11-beta.38",
55
+ "@rynx-ai/emulator": "0.1.11-beta.38",
56
+ "@rynx-ai/plugin-runner": "0.1.11-beta.38",
57
+ "@rynx-ai/plugin-sdk": "0.1.11-beta.38",
58
+ "@rynx-ai/protocol": "0.1.11-beta.38",
59
+ "@rynx-ai/remote-runtime-client": "0.1.11-beta.38",
60
+ "@rynx-ai/server": "0.1.11-beta.38"
61
61
  },
62
62
  "devDependencies": {
63
63
  "@types/ws": "^8.18.1",
64
- "@rynx-ai/plugin-channel-lark": "0.1.11-beta.36"
64
+ "@rynx-ai/plugin-channel-lark": "0.1.11-beta.38"
65
65
  },
66
66
  "scripts": {
67
67
  "build": "rm -rf dist bundled-plugins && tsc -p tsconfig.json && chmod +x dist/index-daemon.js && node ../../scripts/stage-bundled-plugins.mjs",