@nurix/nustack 0.10.0-dev.13 → 0.10.0-dev.15

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 (52) hide show
  1. package/dist/commands/bootstrap.js +4 -4
  2. package/dist/commands/docs.js +13 -13
  3. package/dist/commands/doctor.js +13 -13
  4. package/dist/commands/handoff.js +8 -8
  5. package/dist/commands/init.js +43 -43
  6. package/dist/commands/{workspace-sync.js → project-sync.js} +5 -5
  7. package/dist/commands/{workspace.js → project.js} +133 -133
  8. package/dist/commands/review.js +5 -5
  9. package/dist/commands/search.js +7 -7
  10. package/dist/commands/sessions.js +1 -1
  11. package/dist/commands/sync.js +10 -10
  12. package/dist/commands/toolkit.js +19 -19
  13. package/dist/index.js +29 -29
  14. package/dist/lib/etna/profile.js +9 -9
  15. package/dist/lib/etna/reconcile.js +11 -11
  16. package/dist/lib/git-sync/core/engine.js +4 -4
  17. package/dist/lib/git-sync/inbound/apply_commit.js +3 -3
  18. package/dist/lib/git-sync/inbound/approval.js +1 -1
  19. package/dist/lib/git-sync/inbound/download.js +8 -8
  20. package/dist/lib/git-sync/inbound/event_sync.js +13 -13
  21. package/dist/lib/git-sync/inbound/notification.js +1 -1
  22. package/dist/lib/git-sync/outbound/extraction.js +4 -4
  23. package/dist/lib/git-sync/outbound/history_decision.js +2 -2
  24. package/dist/lib/git-sync/outbound/publisher.js +2 -2
  25. package/dist/lib/git-sync/outbound/queue.js +3 -3
  26. package/dist/lib/git-sync/outbound/upload_client.js +4 -4
  27. package/dist/lib/git-sync/whitelist/index.js +1 -1
  28. package/dist/lib/git-sync/whitelist/types.js +1 -1
  29. package/dist/lib/git-sync/whitelist/validation.js +5 -5
  30. package/dist/lib/lanes/supervisor.js +8 -8
  31. package/dist/lib/local_store/accepted_uploads.js +5 -5
  32. package/dist/lib/local_store/bindings.js +11 -11
  33. package/dist/lib/local_store/index.js +1 -1
  34. package/dist/lib/local_store/migrations.js +13 -0
  35. package/dist/lib/local_store/project_events_cache.js +54 -0
  36. package/dist/lib/nustack_client.js +45 -45
  37. package/dist/lib/onboarding/attach_existing_git.js +7 -7
  38. package/dist/lib/onboarding/binding.js +11 -11
  39. package/dist/lib/onboarding/folder_state.js +10 -10
  40. package/dist/lib/onboarding/handoff_apply.js +2 -2
  41. package/dist/lib/onboarding/init_fresh_folder.js +18 -18
  42. package/dist/lib/onboarding/manifest_writer.js +31 -31
  43. package/dist/lib/onboarding/web_first_attach.js +12 -12
  44. package/dist/lib/{workspace_context.js → project_context.js} +24 -24
  45. package/dist/lib/review/client.js +1 -1
  46. package/dist/lib/session_telemetry.js +2 -2
  47. package/dist/lib/sessions/client.js +8 -8
  48. package/dist/lib/sessions/outsideSessions.js +3 -3
  49. package/dist/lib/telemetry/config_renderer.js +3 -3
  50. package/dist/lib/telemetry/health_report.js +1 -1
  51. package/package.json +1 -1
  52. package/dist/lib/local_store/workspace_events_cache.js +0 -54
@@ -49,11 +49,11 @@ export async function publishCurrentState(store, binding, repo, deps) {
49
49
  repo,
50
50
  client: deps.client,
51
51
  whitelistRef: { current: whitelist },
52
- workspaceId: binding.workspaceId,
52
+ projectId: binding.projectId,
53
53
  now: deps.now ?? Date.now,
54
54
  };
55
55
  for (let staleDepth = 0;; staleDepth++) {
56
- const delta = computeTreeDelta(repo, base, head, ctx.whitelistRef.current, binding.workspaceId);
56
+ const delta = computeTreeDelta(repo, base, head, ctx.whitelistRef.current, binding.projectId);
57
57
  if (!delta.ok)
58
58
  return { ok: false, code: delta.code, detail: delta.path };
59
59
  const result = await pushOperations(ctx, head, base, delta.delta.operations);
@@ -19,7 +19,7 @@ export async function runOutboundPublisher(ctx, runtime) {
19
19
  repo,
20
20
  client: runtime.client,
21
21
  whitelistRef: { current: whitelist },
22
- workspaceId: binding.workspaceId,
22
+ projectId: binding.projectId,
23
23
  now,
24
24
  };
25
25
  for (const commitId of commitIds) {
@@ -32,7 +32,7 @@ export async function runOutboundPublisher(ctx, runtime) {
32
32
  switch (outcome.kind) {
33
33
  case "accepted":
34
34
  updateBindingCursor(store, binding.id, { lastProcessedCommitId: commitId });
35
- emit({ event: "upload_result", bindingId: binding.id, commitId, outcome: "accepted", handoffId: outcome.handoffId, workspaceSequence: outcome.workspaceSequence });
35
+ emit({ event: "upload_result", bindingId: binding.id, commitId, outcome: "accepted", handoffId: outcome.handoffId, projectSequence: outcome.projectSequence });
36
36
  break;
37
37
  case "no_op":
38
38
  updateBindingCursor(store, binding.id, { lastProcessedCommitId: commitId });
@@ -24,7 +24,7 @@ export async function submitHead(ctx, commitId) {
24
24
  return attempt(ctx, commitId, 0);
25
25
  }
26
26
  async function attempt(ctx, commitId, staleDepth) {
27
- const extraction = extractCommitDelta(ctx.repo, commitId, ctx.whitelistRef.current, ctx.workspaceId);
27
+ const extraction = extractCommitDelta(ctx.repo, commitId, ctx.whitelistRef.current, ctx.projectId);
28
28
  if (!extraction.ok) {
29
29
  return blockBinding(ctx, commitId, extraction.code, { path: extraction.path });
30
30
  }
@@ -85,11 +85,11 @@ export async function pushOperations(ctx, commitId, parentCommitId, operations)
85
85
  bindingId: ctx.binding.id,
86
86
  commitId,
87
87
  handoffId: outcome.accepted.handoffId,
88
- workspaceSequence: outcome.accepted.workspaceSequence,
88
+ projectSequence: outcome.accepted.projectSequence,
89
89
  manifestSha256: built.manifestSha256,
90
90
  });
91
91
  deleteUpload(ctx.store, ctx.binding.id, commitId);
92
- return { kind: "accepted", handoffId: outcome.accepted.handoffId, workspaceSequence: outcome.accepted.workspaceSequence };
92
+ return { kind: "accepted", handoffId: outcome.accepted.handoffId, projectSequence: outcome.accepted.projectSequence };
93
93
  }
94
94
  async function repinWhitelist(ctx) {
95
95
  try {
@@ -18,7 +18,7 @@ const TERMINAL_CODES = new Set([
18
18
  "INVALID_STATE",
19
19
  "IDEMPOTENCY_CONFLICT",
20
20
  "HANDOFF_SUPERSEDED",
21
- "WORKSPACE_NOT_FOUND",
21
+ "PROJECT_NOT_FOUND",
22
22
  "DOCUMENT_NOT_FOUND",
23
23
  ]);
24
24
  const AUTH_CODES = new Set(["AUTH_REQUIRED", "ACCESS_TOKEN_EXPIRED", "DEVICE_CREDENTIAL_INVALID"]);
@@ -120,11 +120,11 @@ export function createUploadClient(deps) {
120
120
  const data = (response.body?.data ?? {});
121
121
  if (data.noOp)
122
122
  return { ok: true, value: { noOp: true } };
123
- const workspaceSequence = data.workspaceSequence ?? response.body?.meta?.workspaceSequence;
124
- if (!data.handoffId || typeof workspaceSequence !== "number") {
123
+ const projectSequence = data.projectSequence ?? response.body?.meta?.projectSequence;
124
+ if (!data.handoffId || typeof projectSequence !== "number") {
125
125
  return { ok: false, error: { kind: "terminal", code: "MALFORMED_ACCEPTANCE" } };
126
126
  }
127
- return { ok: true, value: { accepted: { handoffId: data.handoffId, workspaceSequence } } };
127
+ return { ok: true, value: { accepted: { handoffId: data.handoffId, projectSequence } } };
128
128
  });
129
129
  },
130
130
  reportHealth(bindingId, report) {
@@ -1,4 +1,4 @@
1
1
  export { SYNC_PROTOCOL_VERSION, isWhitelistManifest, } from "./types.js";
2
2
  export { matchPath, isProtectedPath, globMatch } from "./matcher.js";
3
- export { validateOperationContent, mediaTypeForPath, frontmatterWorkspaceId, } from "./validation.js";
3
+ export { validateOperationContent, mediaTypeForPath, frontmatterProjectId, } from "./validation.js";
4
4
  export { fetchWhitelist, pinWhitelist, loadPinnedWhitelist, canonicalWhitelistSha256, whitelistCacheFile, WhitelistError, } from "./store.js";
@@ -36,7 +36,7 @@ export function isWhitelistManifest(value) {
36
36
  return (manifest.schemaVersion === 1 &&
37
37
  typeof manifest.manifestId === "string" &&
38
38
  typeof manifest.version === "number" &&
39
- typeof manifest.workspaceId === "string" &&
39
+ typeof manifest.projectId === "string" &&
40
40
  typeof manifest.protocolVersion === "string" &&
41
41
  Array.isArray(manifest.entries) &&
42
42
  manifest.entries.every(isEntry) &&
@@ -14,7 +14,7 @@ export function mediaTypeForPath(path) {
14
14
  const ext = path.slice(dot + 1).toLowerCase();
15
15
  return MEDIA_TYPE_BY_EXTENSION[ext] ?? null;
16
16
  }
17
- export function frontmatterWorkspaceId(text) {
17
+ export function frontmatterProjectId(text) {
18
18
  if (!text.startsWith("---"))
19
19
  return null;
20
20
  const firstNewline = text.indexOf("\n");
@@ -26,7 +26,7 @@ export function frontmatterWorkspaceId(text) {
26
26
  const closeIndex = rest.search(/\n---\s*(\n|$)/);
27
27
  const block = closeIndex < 0 ? rest : rest.slice(0, closeIndex);
28
28
  for (const line of block.split("\n")) {
29
- const match = /^\s*(?:workspace_id|workspaceId|app_id|appId)\s*:\s*(.+?)\s*$/.exec(line);
29
+ const match = /^\s*(?:project_id|projectId|app_id|appId)\s*:\s*(.+?)\s*$/.exec(line);
30
30
  if (match)
31
31
  return match[1].replace(/^["']|["']$/g, "");
32
32
  }
@@ -52,9 +52,9 @@ export function validateOperationContent(input) {
52
52
  }
53
53
  if (text.includes(NUL))
54
54
  return { ok: false, code: "invalid_encoding" };
55
- const declaredWorkspace = frontmatterWorkspaceId(text);
56
- if (declaredWorkspace !== null && declaredWorkspace !== input.workspaceId) {
57
- return { ok: false, code: "cross_workspace_marker" };
55
+ const declaredProject = frontmatterProjectId(text);
56
+ if (declaredProject !== null && declaredProject !== input.projectId) {
57
+ return { ok: false, code: "cross_project_marker" };
58
58
  }
59
59
  return { ok: true };
60
60
  }
@@ -40,7 +40,7 @@ export class LaneSupervisor {
40
40
  registryPath() {
41
41
  return path.join(lanesHome(this.env), "registry.json");
42
42
  }
43
- async boundWorkspaceOf(projectPath) {
43
+ async boundProjectOf(projectPath) {
44
44
  try {
45
45
  const parsed = JSON.parse(await readFile(path.join(projectPath, ".nustack", "workspace.json"), "utf8"));
46
46
  const id = parsed?.workspaceId;
@@ -52,10 +52,10 @@ export class LaneSupervisor {
52
52
  }
53
53
  async moveLaneSession(record, to) {
54
54
  const client = this.options.sessions;
55
- if (!client || !record.workspaceId)
55
+ if (!client || !record.projectId)
56
56
  return;
57
57
  try {
58
- await client.transitionSession(record.workspaceId, record.laneId, to);
58
+ await client.transitionSession(record.projectId, record.laneId, to);
59
59
  }
60
60
  catch {
61
61
  }
@@ -153,7 +153,7 @@ export class LaneSupervisor {
153
153
  providerSessionId: null,
154
154
  createdAt: new Date().toISOString(),
155
155
  status: "open",
156
- workspaceId: await this.boundWorkspaceOf(projectPath),
156
+ projectId: await this.boundProjectOf(projectPath),
157
157
  };
158
158
  const adapterOptions = {
159
159
  cwd: worktree.worktreePath,
@@ -183,8 +183,8 @@ export class LaneSupervisor {
183
183
  record.providerSessionId = sessionId;
184
184
  void this.updateRegistry((lanes) => lanes.map((row) => (row.laneId === laneId ? { ...row, providerSessionId: sessionId } : row)));
185
185
  const client = this.options.sessions;
186
- if (client && record.workspaceId) {
187
- void client.setVendorSessionId(record.workspaceId, laneId, sessionId).catch(() => undefined);
186
+ if (client && record.projectId) {
187
+ void client.setVendorSessionId(record.projectId, laneId, sessionId).catch(() => undefined);
188
188
  }
189
189
  },
190
190
  onExit: (code) => {
@@ -207,9 +207,9 @@ export class LaneSupervisor {
207
207
  }
208
208
  this.lanes.set(laneId, { adapter, record });
209
209
  await this.updateRegistry((lanes) => [...lanes.filter((row) => row.laneId !== laneId), record]);
210
- if (this.options.sessions && record.workspaceId) {
210
+ if (this.options.sessions && record.projectId) {
211
211
  try {
212
- await this.options.sessions.createSession(record.workspaceId, {
212
+ await this.options.sessions.createSession(record.projectId, {
213
213
  id: laneId,
214
214
  startedBy: "person",
215
215
  substrate: "device",
@@ -3,7 +3,7 @@ function mapRow(row) {
3
3
  bindingId: row.binding_id,
4
4
  commitId: row.commit_id,
5
5
  handoffId: row.handoff_id,
6
- workspaceSequence: row.workspace_sequence,
6
+ projectSequence: row.project_sequence,
7
7
  manifestSha256: row.manifest_sha256,
8
8
  createdAtMs: row.created_at_ms,
9
9
  };
@@ -11,16 +11,16 @@ function mapRow(row) {
11
11
  export function recordAcceptedUpload(store, input) {
12
12
  store.db
13
13
  .prepare(`INSERT INTO local_accepted_uploads (
14
- binding_id, commit_id, handoff_id, workspace_sequence, manifest_sha256, created_at_ms
14
+ binding_id, commit_id, handoff_id, project_sequence, manifest_sha256, created_at_ms
15
15
  ) VALUES (
16
- :binding_id, :commit_id, :handoff_id, :workspace_sequence, :manifest_sha256, :created_at_ms
16
+ :binding_id, :commit_id, :handoff_id, :project_sequence, :manifest_sha256, :created_at_ms
17
17
  )
18
18
  ON CONFLICT(binding_id, commit_id) DO NOTHING`)
19
19
  .run({
20
20
  binding_id: input.bindingId,
21
21
  commit_id: input.commitId,
22
22
  handoff_id: input.handoffId,
23
- workspace_sequence: input.workspaceSequence,
23
+ project_sequence: input.projectSequence,
24
24
  manifest_sha256: input.manifestSha256,
25
25
  created_at_ms: Date.now(),
26
26
  });
@@ -42,7 +42,7 @@ export function latestAcceptedUpload(store, bindingId) {
42
42
  const row = store.db
43
43
  .prepare(`SELECT * FROM local_accepted_uploads
44
44
  WHERE binding_id = :binding_id
45
- ORDER BY workspace_sequence DESC, created_at_ms DESC
45
+ ORDER BY project_sequence DESC, created_at_ms DESC
46
46
  LIMIT 1`)
47
47
  .get({ binding_id: bindingId });
48
48
  return row ? mapRow(row) : null;
@@ -3,7 +3,7 @@ import { LIVE_BINDING_STATUSES } from "./sync_vocabulary.js";
3
3
  function mapRow(row) {
4
4
  return {
5
5
  id: row.id,
6
- workspaceId: row.workspace_id,
6
+ projectId: row.project_id,
7
7
  organizationId: row.organization_id,
8
8
  repositoryRoot: row.repository_root,
9
9
  gitDir: row.git_dir,
@@ -11,7 +11,7 @@ function mapRow(row) {
11
11
  repositoryFingerprint: row.repository_fingerprint,
12
12
  boundBranch: row.bound_branch,
13
13
  lastProcessedCommitId: row.last_processed_commit_id ?? null,
14
- lastAppliedWorkspaceSequence: row.last_applied_workspace_sequence,
14
+ lastAppliedProjectSequence: row.last_applied_project_sequence,
15
15
  whitelistManifestId: row.whitelist_manifest_id ?? null,
16
16
  whitelistManifestSha256: row.whitelist_manifest_sha256 ?? null,
17
17
  whitelistCachePath: row.whitelist_cache_path ?? null,
@@ -28,21 +28,21 @@ export function insertBinding(store, input) {
28
28
  const now = Date.now();
29
29
  store.db
30
30
  .prepare(`INSERT INTO local_bindings (
31
- id, workspace_id, organization_id, repository_root, git_dir, git_common_dir,
31
+ id, project_id, organization_id, repository_root, git_dir, git_common_dir,
32
32
  repository_fingerprint, bound_branch, last_processed_commit_id,
33
- last_applied_workspace_sequence, whitelist_manifest_id, whitelist_manifest_sha256,
33
+ last_applied_project_sequence, whitelist_manifest_id, whitelist_manifest_sha256,
34
34
  whitelist_cache_path, etna_profile_json, status, last_error_code,
35
35
  last_error_detail, created_at_ms, updated_at_ms
36
36
  ) VALUES (
37
- :id, :workspace_id, :organization_id, :repository_root, :git_dir, :git_common_dir,
37
+ :id, :project_id, :organization_id, :repository_root, :git_dir, :git_common_dir,
38
38
  :repository_fingerprint, :bound_branch, :last_processed_commit_id,
39
- :last_applied_workspace_sequence, :whitelist_manifest_id, :whitelist_manifest_sha256,
39
+ :last_applied_project_sequence, :whitelist_manifest_id, :whitelist_manifest_sha256,
40
40
  :whitelist_cache_path, :etna_profile_json, :status, NULL,
41
41
  NULL, :created_at_ms, :updated_at_ms
42
42
  )`)
43
43
  .run({
44
44
  id: input.id,
45
- workspace_id: input.workspaceId,
45
+ project_id: input.projectId,
46
46
  organization_id: input.organizationId,
47
47
  repository_root: input.repositoryRoot,
48
48
  git_dir: input.gitDir,
@@ -50,7 +50,7 @@ export function insertBinding(store, input) {
50
50
  repository_fingerprint: input.repositoryFingerprint,
51
51
  bound_branch: input.boundBranch,
52
52
  last_processed_commit_id: input.lastProcessedCommitId ?? null,
53
- last_applied_workspace_sequence: input.lastAppliedWorkspaceSequence ?? 0,
53
+ last_applied_project_sequence: input.lastAppliedProjectSequence ?? 0,
54
54
  whitelist_manifest_id: input.whitelistManifestId ?? null,
55
55
  whitelist_manifest_sha256: input.whitelistManifestSha256 ?? null,
56
56
  whitelist_cache_path: input.whitelistCachePath ?? null,
@@ -103,12 +103,12 @@ export function updateBindingCursor(store, bindingId, patch) {
103
103
  WHERE id = :id`)
104
104
  .run({ commit: patch.lastProcessedCommitId ?? null, now, id: bindingId });
105
105
  }
106
- if (patch.lastAppliedWorkspaceSequence !== undefined) {
106
+ if (patch.lastAppliedProjectSequence !== undefined) {
107
107
  store.db
108
108
  .prepare(`UPDATE local_bindings
109
- SET last_applied_workspace_sequence = :seq, updated_at_ms = :now
109
+ SET last_applied_project_sequence = :seq, updated_at_ms = :now
110
110
  WHERE id = :id`)
111
- .run({ seq: patch.lastAppliedWorkspaceSequence, now, id: bindingId });
111
+ .run({ seq: patch.lastAppliedProjectSequence, now, id: bindingId });
112
112
  }
113
113
  return getBindingById(store, bindingId);
114
114
  });
@@ -10,5 +10,5 @@ export * from "./recovery_bundles.js";
10
10
  export * from "./pending_uploads.js";
11
11
  export * from "./history_decisions.js";
12
12
  export * from "./accepted_uploads.js";
13
- export * from "./workspace_events_cache.js";
13
+ export * from "./project_events_cache.js";
14
14
  export * from "./filebeat_state.js";
@@ -193,6 +193,18 @@ const MIGRATION_V6 = {
193
193
  `CREATE INDEX local_bindings_workspace_idx ON local_bindings (workspace_id)`,
194
194
  ],
195
195
  };
196
+ const MIGRATION_V7 = {
197
+ version: 7,
198
+ statements: [
199
+ `ALTER TABLE local_bindings RENAME COLUMN workspace_id TO project_id`,
200
+ `ALTER TABLE local_bindings RENAME COLUMN last_applied_workspace_sequence TO last_applied_project_sequence`,
201
+ `ALTER TABLE cached_workspace_events RENAME COLUMN workspace_sequence TO project_sequence`,
202
+ `ALTER TABLE local_accepted_uploads RENAME COLUMN workspace_sequence TO project_sequence`,
203
+ `ALTER TABLE cached_workspace_events RENAME TO cached_project_events`,
204
+ `DROP INDEX IF EXISTS local_bindings_workspace_idx`,
205
+ `CREATE INDEX local_bindings_project_idx ON local_bindings (project_id)`,
206
+ ],
207
+ };
196
208
  export const MIGRATIONS = [
197
209
  MIGRATION_V1,
198
210
  MIGRATION_V2,
@@ -200,6 +212,7 @@ export const MIGRATIONS = [
200
212
  MIGRATION_V4,
201
213
  MIGRATION_V5,
202
214
  MIGRATION_V6,
215
+ MIGRATION_V7,
203
216
  ];
204
217
  export const LATEST_SCHEMA_VERSION = MIGRATIONS[MIGRATIONS.length - 1].version;
205
218
  export class LocalSchemaTooNewError extends Error {
@@ -0,0 +1,54 @@
1
+ export const DEFAULT_PROJECT_EVENT_KEEP_COUNT = 500;
2
+ function mapRow(row) {
3
+ return {
4
+ bindingId: row.binding_id,
5
+ projectSequence: row.project_sequence,
6
+ eventType: row.event_type,
7
+ payloadJson: row.payload_json ?? null,
8
+ appliedAtMs: row.applied_at_ms ?? null,
9
+ createdAtMs: row.created_at_ms,
10
+ };
11
+ }
12
+ export function cacheProjectEvents(store, bindingId, events) {
13
+ const now = Date.now();
14
+ const insert = store.db.prepare(`INSERT INTO cached_project_events (
15
+ binding_id, project_sequence, event_type, payload_json, applied_at_ms, created_at_ms
16
+ ) VALUES (
17
+ :binding_id, :project_sequence, :event_type, :payload_json, :applied_at_ms, :created_at_ms
18
+ )
19
+ ON CONFLICT(binding_id, project_sequence) DO NOTHING`);
20
+ for (const event of events) {
21
+ insert.run({
22
+ binding_id: bindingId,
23
+ project_sequence: event.projectSequence,
24
+ event_type: event.eventType,
25
+ payload_json: event.payloadJson ?? null,
26
+ applied_at_ms: event.appliedAtMs ?? null,
27
+ created_at_ms: now,
28
+ });
29
+ }
30
+ }
31
+ export function latestCachedSequence(store, bindingId) {
32
+ const row = store.db
33
+ .prepare("SELECT MAX(project_sequence) AS latest FROM cached_project_events WHERE binding_id = :binding_id")
34
+ .get({ binding_id: bindingId });
35
+ return row?.latest ?? null;
36
+ }
37
+ export function listCachedProjectEvents(store, bindingId) {
38
+ const rows = store.db
39
+ .prepare("SELECT * FROM cached_project_events WHERE binding_id = :binding_id ORDER BY project_sequence ASC")
40
+ .all({ binding_id: bindingId });
41
+ return rows.map(mapRow);
42
+ }
43
+ export function pruneProjectEvents(store, bindingId, keepCount = DEFAULT_PROJECT_EVENT_KEEP_COUNT) {
44
+ store.db
45
+ .prepare(`DELETE FROM cached_project_events
46
+ WHERE binding_id = :binding_id
47
+ AND project_sequence NOT IN (
48
+ SELECT project_sequence FROM cached_project_events
49
+ WHERE binding_id = :binding_id
50
+ ORDER BY project_sequence DESC
51
+ LIMIT :keep
52
+ )`)
53
+ .run({ binding_id: bindingId, keep: keepCount });
54
+ }
@@ -60,7 +60,7 @@ export async function exchangeServiceCredential(baseUrl, accessToken, params) {
60
60
  const response = await authorizedFetch(`${baseUrl}/api/v1/keys/exchange`, {
61
61
  method: "POST",
62
62
  headers: { Authorization: `Bearer ${accessToken}`, "Content-Type": "application/json" },
63
- body: JSON.stringify({ workspaceId: params.workspaceId, service: params.service }),
63
+ body: JSON.stringify({ projectId: params.projectId, service: params.service }),
64
64
  signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
65
65
  });
66
66
  const payload = (await response.json().catch(() => null));
@@ -71,7 +71,7 @@ export async function exchangeServiceCredential(baseUrl, accessToken, params) {
71
71
  }
72
72
  export async function fetchServiceCredential(baseUrl, accessToken, params) {
73
73
  const url = new URL(`${baseUrl}/api/v1/keys/credentials`);
74
- url.searchParams.set("workspaceId", assertSafeOpaqueId("The Workspace id", params.workspaceId));
74
+ url.searchParams.set("projectId", assertSafeOpaqueId("The Project id", params.projectId));
75
75
  url.searchParams.set("service", assertSafeOpaqueId("The Service slug", params.service));
76
76
  const response = await authorizedFetch(url, {
77
77
  headers: { Authorization: `Bearer ${accessToken}` },
@@ -83,9 +83,9 @@ export async function fetchServiceCredential(baseUrl, accessToken, params) {
83
83
  }
84
84
  return narrowProjection(payload, params.service);
85
85
  }
86
- export async function listWorkspaceIntegrations(baseUrl, accessToken, workspaceId) {
86
+ export async function listProjectIntegrations(baseUrl, accessToken, projectId) {
87
87
  const url = new URL(`${baseUrl}/api/v1/keys/integrations`);
88
- url.searchParams.set("workspaceId", assertSafeOpaqueId("The Workspace id", workspaceId));
88
+ url.searchParams.set("projectId", assertSafeOpaqueId("The Project id", projectId));
89
89
  const response = await authorizedFetch(url, {
90
90
  headers: { Authorization: `Bearer ${accessToken}` },
91
91
  signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
@@ -101,7 +101,7 @@ export async function fetchBrokeredCredential(baseUrl, accessToken, params) {
101
101
  const response = await authorizedFetch(`${baseUrl}/api/v1/keys/broker/${encodeURIComponent(service)}`, {
102
102
  method: "POST",
103
103
  headers: { Authorization: `Bearer ${accessToken}`, "Content-Type": "application/json" },
104
- body: JSON.stringify({ workspaceId: params.workspaceId }),
104
+ body: JSON.stringify({ projectId: params.projectId }),
105
105
  signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
106
106
  });
107
107
  const payload = (await response.json().catch(() => null));
@@ -110,8 +110,8 @@ export async function fetchBrokeredCredential(baseUrl, accessToken, params) {
110
110
  }
111
111
  return { url: payload.url ?? null, key: payload.key, meta: payload.meta ?? null, grantId: payload.grantId };
112
112
  }
113
- export async function searchWorkspaces(baseUrl, query, opts = {}) {
114
- const url = new URL(`${baseUrl}/public/v1/workspaces`);
113
+ export async function searchProjects(baseUrl, query, opts = {}) {
114
+ const url = new URL(`${baseUrl}/public/v1/projects`);
115
115
  url.searchParams.set("q", query);
116
116
  if (opts.category)
117
117
  url.searchParams.set("category", opts.category);
@@ -120,7 +120,7 @@ export async function searchWorkspaces(baseUrl, query, opts = {}) {
120
120
  const response = await authorizedFetch(url, { signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS) });
121
121
  const payload = (await response.json().catch(() => null));
122
122
  if (!response.ok || !Array.isArray(payload?.data?.items)) {
123
- throw new Error(errorMessage(payload, response.status, "workspace search failed"));
123
+ throw new Error(errorMessage(payload, response.status, "project search failed"));
124
124
  }
125
125
  return payload.data.items;
126
126
  }
@@ -190,10 +190,10 @@ export async function acknowledgeBindingCursor(baseUrl, accessToken, params) {
190
190
  signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
191
191
  });
192
192
  const payload = (await response.json().catch(() => null));
193
- if (!response.ok || typeof payload?.data?.lastAppliedWorkspaceSequence !== "number") {
193
+ if (!response.ok || typeof payload?.data?.lastAppliedProjectSequence !== "number") {
194
194
  throw new Error(errorMessage(payload, response.status, "cursor acknowledgement failed"));
195
195
  }
196
- return payload.data.lastAppliedWorkspaceSequence;
196
+ return payload.data.lastAppliedProjectSequence;
197
197
  }
198
198
  export async function fetchSyncWhitelist(baseUrl, accessToken, bindingId) {
199
199
  const safeId = assertSafeOpaqueId("The binding id", bindingId);
@@ -295,31 +295,31 @@ export async function createOrganization(baseUrl, accessToken, params, idempoten
295
295
  }
296
296
  return payload.data;
297
297
  }
298
- export async function listWorkspaces(baseUrl, accessToken, organizationId) {
298
+ export async function listProjects(baseUrl, accessToken, organizationId) {
299
299
  const safeId = assertSafeOpaqueId("The Organization id", organizationId);
300
- const response = await authorizedFetch(`${baseUrl}/api/v2/workspaces`, {
300
+ const response = await authorizedFetch(`${baseUrl}/api/v2/projects`, {
301
301
  headers: { ...authHeaders(accessToken), "X-NuStack-Organization": safeId },
302
302
  signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
303
303
  });
304
304
  const payload = (await response.json().catch(() => null));
305
305
  if (!response.ok || !Array.isArray(payload?.data?.items)) {
306
- throw new Error(errorMessage(payload, response.status, "workspace list failed"));
306
+ throw new Error(errorMessage(payload, response.status, "project list failed"));
307
307
  }
308
308
  return payload.data.items;
309
309
  }
310
- export async function getWorkspace(baseUrl, accessToken, workspaceId) {
311
- const safeId = assertSafeOpaqueId("The Workspace id", workspaceId);
312
- const response = await authorizedFetch(`${baseUrl}/api/v2/workspaces/${encodeURIComponent(safeId)}`, {
310
+ export async function getProject(baseUrl, accessToken, projectId) {
311
+ const safeId = assertSafeOpaqueId("The Project id", projectId);
312
+ const response = await authorizedFetch(`${baseUrl}/api/v2/projects/${encodeURIComponent(safeId)}`, {
313
313
  headers: authHeaders(accessToken),
314
314
  signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
315
315
  });
316
316
  const payload = (await response.json().catch(() => null));
317
317
  if (!response.ok || !payload?.data?.id) {
318
- throw new Error(errorMessage(payload, response.status, "workspace fetch failed"));
318
+ throw new Error(errorMessage(payload, response.status, "project fetch failed"));
319
319
  }
320
320
  return payload.data;
321
321
  }
322
- export function mintWorkspaceId() {
322
+ export function mintProjectId() {
323
323
  const bytes = crypto.getRandomValues(new Uint8Array(16));
324
324
  const ms = Date.now();
325
325
  bytes[0] = Math.floor(ms / 2 ** 40) & 0xff;
@@ -360,9 +360,9 @@ async function* readSseFrames(body) {
360
360
  for (const block of parser.flush())
361
361
  yield toFrame(block);
362
362
  }
363
- export async function createWorkspaceViaAsk(baseUrl, accessToken, organizationId, prompt, opts = {}) {
363
+ export async function createProjectViaAsk(baseUrl, accessToken, organizationId, prompt, opts = {}) {
364
364
  const safeOrganization = assertSafeOpaqueId("The Organization id", organizationId);
365
- const workspaceId = opts.workspaceId ?? mintWorkspaceId();
365
+ const projectId = opts.projectId ?? mintProjectId();
366
366
  const fetchImpl = opts.fetchImpl ?? fetch;
367
367
  const response = await fetchImpl(`${baseUrl}/api/v2/ask`, {
368
368
  method: "POST",
@@ -371,11 +371,11 @@ export async function createWorkspaceViaAsk(baseUrl, accessToken, organizationId
371
371
  "Content-Type": "application/json",
372
372
  "X-NuStack-Organization": safeOrganization,
373
373
  },
374
- body: JSON.stringify({ prompt, workspaceId }),
374
+ body: JSON.stringify({ prompt, projectId }),
375
375
  });
376
376
  if (!response.ok || !response.body) {
377
377
  const payload = (await response.json().catch(() => null));
378
- throw new Error(errorMessage(payload, response.status, "workspace create failed"));
378
+ throw new Error(errorMessage(payload, response.status, "project create failed"));
379
379
  }
380
380
  let started = false;
381
381
  let turnId = null;
@@ -384,7 +384,7 @@ export async function createWorkspaceViaAsk(baseUrl, accessToken, organizationId
384
384
  if (frame.event === "start") {
385
385
  started = true;
386
386
  turnId = typeof frame.data.turnId === "string" ? frame.data.turnId : null;
387
- opts.onPhase?.("Workspace created — writing the first document");
387
+ opts.onPhase?.("Project created — writing the first document");
388
388
  }
389
389
  else if (frame.event === "thinking" && typeof frame.data.message === "string") {
390
390
  opts.onPhase?.(frame.data.message);
@@ -392,7 +392,7 @@ export async function createWorkspaceViaAsk(baseUrl, accessToken, organizationId
392
392
  else if (frame.event === "error") {
393
393
  const message = typeof frame.data.message === "string" ? frame.data.message : "generation failed";
394
394
  if (!started)
395
- throw new Error(`workspace create failed: ${message}`);
395
+ throw new Error(`project create failed: ${message}`);
396
396
  generationError = message;
397
397
  break;
398
398
  }
@@ -401,12 +401,12 @@ export async function createWorkspaceViaAsk(baseUrl, accessToken, organizationId
401
401
  }
402
402
  }
403
403
  if (!started)
404
- throw new Error("workspace create failed: the stream ended before the Workspace was reported");
405
- return { workspaceId, turnId, generationError };
404
+ throw new Error("project create failed: the stream ended before the Project was reported");
405
+ return { projectId, turnId, generationError };
406
406
  }
407
- async function postWorkspacePublication(baseUrl, accessToken, workspaceId, action, body, idempotencyKey) {
408
- const safeId = assertSafeOpaqueId("The Workspace id", workspaceId);
409
- const response = await authorizedFetch(`${baseUrl}/api/v2/workspaces/${encodeURIComponent(safeId)}/${action}`, {
407
+ async function postProjectPublication(baseUrl, accessToken, projectId, action, body, idempotencyKey) {
408
+ const safeId = assertSafeOpaqueId("The Project id", projectId);
409
+ const response = await authorizedFetch(`${baseUrl}/api/v2/projects/${encodeURIComponent(safeId)}/${action}`, {
410
410
  method: "POST",
411
411
  headers: mutationHeaders(accessToken, idempotencyKey),
412
412
  body: JSON.stringify(body),
@@ -414,19 +414,19 @@ async function postWorkspacePublication(baseUrl, accessToken, workspaceId, actio
414
414
  });
415
415
  const payload = (await response.json().catch(() => null));
416
416
  if (!response.ok || payload?.data === undefined) {
417
- throw new Error(errorMessage(payload, response.status, `workspace ${action} failed`));
417
+ throw new Error(errorMessage(payload, response.status, `project ${action} failed`));
418
418
  }
419
419
  return payload.data;
420
420
  }
421
- export async function publishWorkspace(baseUrl, accessToken, workspaceId, profile, idempotencyKey) {
422
- return postWorkspacePublication(baseUrl, accessToken, workspaceId, "publish", profile, idempotencyKey);
421
+ export async function publishProject(baseUrl, accessToken, projectId, profile, idempotencyKey) {
422
+ return postProjectPublication(baseUrl, accessToken, projectId, "publish", profile, idempotencyKey);
423
423
  }
424
- export async function unpublishWorkspace(baseUrl, accessToken, workspaceId, idempotencyKey) {
425
- return postWorkspacePublication(baseUrl, accessToken, workspaceId, "unpublish", {}, idempotencyKey);
424
+ export async function unpublishProject(baseUrl, accessToken, projectId, idempotencyKey) {
425
+ return postProjectPublication(baseUrl, accessToken, projectId, "unpublish", {}, idempotencyKey);
426
426
  }
427
- export async function listDocuments(baseUrl, accessToken, workspaceId) {
428
- const safeId = assertSafeOpaqueId("The Workspace id", workspaceId);
429
- const response = await authorizedFetch(`${baseUrl}/api/v2/documents?workspaceId=${encodeURIComponent(safeId)}`, {
427
+ export async function listDocuments(baseUrl, accessToken, projectId) {
428
+ const safeId = assertSafeOpaqueId("The Project id", projectId);
429
+ const response = await authorizedFetch(`${baseUrl}/api/v2/documents?projectId=${encodeURIComponent(safeId)}`, {
430
430
  headers: authHeaders(accessToken),
431
431
  signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
432
432
  });
@@ -448,9 +448,9 @@ export async function getDocument(baseUrl, accessToken, documentId) {
448
448
  }
449
449
  return payload.data;
450
450
  }
451
- export async function listHandoffs(baseUrl, accessToken, workspaceId) {
452
- const safeId = assertSafeOpaqueId("The Workspace id", workspaceId);
453
- const response = await authorizedFetch(`${baseUrl}/api/v2/workspaces/${encodeURIComponent(safeId)}/handoffs`, {
451
+ export async function listHandoffs(baseUrl, accessToken, projectId) {
452
+ const safeId = assertSafeOpaqueId("The Project id", projectId);
453
+ const response = await authorizedFetch(`${baseUrl}/api/v2/projects/${encodeURIComponent(safeId)}/handoffs`, {
454
454
  headers: authHeaders(accessToken),
455
455
  signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
456
456
  });
@@ -472,13 +472,13 @@ export async function getHandoff(baseUrl, accessToken, handoffId) {
472
472
  }
473
473
  return payload.data;
474
474
  }
475
- export async function latestHandoff(baseUrl, accessToken, workspaceId) {
476
- const handoffs = await listHandoffs(baseUrl, accessToken, workspaceId);
475
+ export async function latestHandoff(baseUrl, accessToken, projectId) {
476
+ const handoffs = await listHandoffs(baseUrl, accessToken, projectId);
477
477
  return handoffs.length > 0 ? handoffs[0] : null;
478
478
  }
479
- export async function createBinding(baseUrl, accessToken, workspaceId, input, idempotencyKey) {
480
- const safeId = assertSafeOpaqueId("The Workspace id", workspaceId);
481
- const response = await authorizedFetch(`${baseUrl}/api/v2/workspaces/${encodeURIComponent(safeId)}/bindings`, {
479
+ export async function createBinding(baseUrl, accessToken, projectId, input, idempotencyKey) {
480
+ const safeId = assertSafeOpaqueId("The Project id", projectId);
481
+ const response = await authorizedFetch(`${baseUrl}/api/v2/projects/${encodeURIComponent(safeId)}/bindings`, {
482
482
  method: "POST",
483
483
  headers: mutationHeaders(accessToken, idempotencyKey),
484
484
  body: JSON.stringify({