@trydreamstate/cli 1.3.13 → 1.3.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 (53) hide show
  1. package/README.md +5 -1
  2. package/agent-plugin/.claude-plugin/plugin.json +1 -1
  3. package/agent-plugin/.codex-plugin/plugin.json +1 -1
  4. package/agent-plugin/skills/diagnostics/SKILL.md +2 -0
  5. package/agent-plugin/skills/first-win/SKILL.md +1 -1
  6. package/agent-plugin/skills/list-building/SKILL.md +4 -0
  7. package/agent-plugin/skills/list-building/column-authoring.md +1 -1
  8. package/agent-plugin/skills/list-building/incoming-people.md +2 -2
  9. package/agent-plugin/skills/outreach/SKILL.md +6 -6
  10. package/agent-plugin/skills/outreach/enrollment.md +3 -3
  11. package/agent-plugin/skills/outreach/message-copy.md +1 -1
  12. package/agent-plugin/skills/social/SKILL.md +6 -2
  13. package/agent-plugin/skills/workflows/lifecycle-and-enrollment.md +1 -1
  14. package/dist/client.d.ts +2 -2
  15. package/dist/client.d.ts.map +1 -1
  16. package/dist/client.js +48 -5
  17. package/dist/commands/engage.d.ts +2 -0
  18. package/dist/commands/engage.d.ts.map +1 -0
  19. package/dist/commands/engage.js +6 -0
  20. package/dist/commands/genericPack.d.ts +1 -1
  21. package/dist/commands/genericPack.d.ts.map +1 -1
  22. package/dist/commands/shared.d.ts +1 -1
  23. package/dist/commands/shared.d.ts.map +1 -1
  24. package/dist/commands/shared.js +14 -4
  25. package/dist/config.d.ts +1 -0
  26. package/dist/config.d.ts.map +1 -1
  27. package/dist/config.js +9 -0
  28. package/dist/errors.d.ts +8 -0
  29. package/dist/errors.d.ts.map +1 -1
  30. package/dist/errors.js +4 -0
  31. package/dist/gatedRun.d.ts +13 -5
  32. package/dist/gatedRun.d.ts.map +1 -1
  33. package/dist/gatedRun.js +46 -11
  34. package/dist/generated/packVerbs.generated.json +1 -5
  35. package/dist/idempotency.d.ts.map +1 -1
  36. package/dist/idempotency.js +47 -17
  37. package/dist/index.d.ts.map +1 -1
  38. package/dist/index.js +39 -5
  39. package/dist/runEnvelopeStatus.d.ts +53 -0
  40. package/dist/runEnvelopeStatus.d.ts.map +1 -0
  41. package/dist/runEnvelopeStatus.js +83 -0
  42. package/dist/runWait.d.ts.map +1 -1
  43. package/dist/runWait.js +9 -11
  44. package/dist/skillInstaller.d.ts.map +1 -1
  45. package/dist/skillInstaller.js +10 -0
  46. package/dist/tableFilePreview.d.ts.map +1 -1
  47. package/dist/tableFilePreview.js +21 -3
  48. package/dist/tablesImport.d.ts.map +1 -1
  49. package/dist/tablesImport.js +13 -3
  50. package/dist/turnCache.d.ts +12 -1
  51. package/dist/turnCache.d.ts.map +1 -1
  52. package/dist/turnCache.js +104 -25
  53. package/package.json +1 -1
@@ -203,7 +203,7 @@ function unzipEntries(buffer) {
203
203
  const localOffset = buffer.readUInt32LE(offset + 42);
204
204
  const name = buffer.toString('utf8', offset + 46, offset + 46 + nameLength);
205
205
  offset += 46 + nameLength + extraLength + commentLength;
206
- if (!/xl\/(sharedStrings\.xml|worksheets\/sheet\d+\.xml)$/.test(name))
206
+ if (!/xl\/(sharedStrings\.xml|workbook\.xml|_rels\/workbook\.xml\.rels|worksheets\/sheet\d+\.xml)$/.test(name))
207
207
  continue;
208
208
  if (localOffset + 30 > buffer.length)
209
209
  throw new Error('xlsx local header is outside the file');
@@ -243,9 +243,23 @@ function sharedStringTable(xml) {
243
243
  */
244
244
  function readXlsxRows(buffer, rowLimit) {
245
245
  const entries = unzipEntries(buffer);
246
- const sheetName = [...entries.keys()]
246
+ let sheetName = [...entries.keys()]
247
247
  .filter((name) => /xl\/worksheets\/sheet\d+\.xml$/.test(name))
248
248
  .sort()[0];
249
+ const workbook = entries.get('xl/workbook.xml');
250
+ const relationships = entries.get('xl/_rels/workbook.xml.rels');
251
+ if (workbook && relationships) {
252
+ const sheet = /<(?:[\w-]+:)?sheet\b([^>]*)\/?\s*>/.exec(workbook.toString('utf8'));
253
+ const id = /\br:id=["']([^"']+)["']/.exec(sheet?.[1] ?? '')?.[1];
254
+ const relationship = [...relationships.toString('utf8').matchAll(/<(?:[\w-]+:)?Relationship\b([^>]*)\/?\s*>/g)]
255
+ .find((match) => /\bId=["']([^"']+)["']/.exec(match[1])?.[1] === id);
256
+ const target = /\bTarget=["']([^"']+)["']/.exec(relationship?.[1] ?? '')?.[1];
257
+ if (!id || !target)
258
+ throw new Error('xlsx file has an invalid first worksheet relationship');
259
+ sheetName = target.startsWith('/') ? target.slice(1) : `xl/${target.replace(/^\.\//, '')}`;
260
+ if (!entries.has(sheetName))
261
+ throw new Error('xlsx file has a missing first worksheet');
262
+ }
249
263
  if (!sheetName)
250
264
  throw new Error('xlsx file has no worksheet');
251
265
  const sheet = entries.get(sheetName).toString('utf8');
@@ -282,7 +296,11 @@ function readXlsxRows(buffer, rowLimit) {
282
296
  const rowNumbers = [...grid.keys()].sort((left, right) => left - right);
283
297
  if (rowNumbers.length === 0)
284
298
  throw new Error('File has no header row');
285
- const width = Math.max(...[...grid.values()].map((cells) => Math.max(-1, ...cells.keys()))) + 1;
299
+ let width = 0;
300
+ for (const cells of grid.values())
301
+ for (const k of cells.keys())
302
+ if (k + 1 > width)
303
+ width = k + 1;
286
304
  const toRow = (cells) => Array.from({ length: width }, (_, index) => cells?.get(index) ?? '');
287
305
  const totalRows = [...sheet.matchAll(/<(?:[\w-]+:)?row\b/g)].length;
288
306
  return { rows: rowNumbers.map((number) => toRow(grid.get(number))), totalRows };
@@ -1 +1 @@
1
- {"version":3,"file":"tablesImport.d.ts","sourceRoot":"","sources":["../src/tablesImport.ts"],"names":[],"mappings":"AAIA,OAAO,EAAkB,KAAK,eAAe,EAAE,MAAM,YAAY,CAAC;AAIlE;;;;;;;;;;;;;;;GAeG;AAEH,eAAO,MAAM,gBAAgB,OAAQ,CAAC;AACtC,kFAAkF;AAClF,eAAO,MAAM,iBAAiB,UAAY,CAAC;AAC3C,wEAAwE;AACxE,eAAO,MAAM,eAAe,MAAM,CAAC;AACnC,+EAA+E;AAC/E,eAAO,MAAM,mBAAmB,SAAU,CAAC;AAY3C,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,KAAK,GAAG,MAAM,GAAG,OAAO,GAAG,MAAM,CAAC;IAC3C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,QAAQ,GAAG,SAAS,CAAC;IAC/B,mBAAmB,CAAC,EAAE,OAAO,CAAC;CAC/B;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,QAAQ,GAAG,SAAS,CAAC;IAC/B,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,eAAe,EAAE,MAAM,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC;CACpB;AAuBD;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAoB1E;AAMD,6HAA6H;AAC7H,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,SAAS,MAAM,EAAE,EAC1B,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,EACzC,OAAO,EAAE,QAAQ,GAAG,SAAS,GAC5B,MAAM,GAAG,IAAI,CAMf;AAkCD,wBAAsB,gBAAgB,CAAC,OAAO,EAAE,eAAe,EAAE,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAiJ1H"}
1
+ {"version":3,"file":"tablesImport.d.ts","sourceRoot":"","sources":["../src/tablesImport.ts"],"names":[],"mappings":"AAIA,OAAO,EAAkB,KAAK,eAAe,EAAE,MAAM,YAAY,CAAC;AAIlE;;;;;;;;;;;;;;;GAeG;AAEH,eAAO,MAAM,gBAAgB,OAAQ,CAAC;AACtC,kFAAkF;AAClF,eAAO,MAAM,iBAAiB,UAAY,CAAC;AAC3C,wEAAwE;AACxE,eAAO,MAAM,eAAe,MAAM,CAAC;AACnC,+EAA+E;AAC/E,eAAO,MAAM,mBAAmB,SAAU,CAAC;AAY3C,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,KAAK,GAAG,MAAM,GAAG,OAAO,GAAG,MAAM,CAAC;IAC3C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,QAAQ,GAAG,SAAS,CAAC;IAC/B,mBAAmB,CAAC,EAAE,OAAO,CAAC;CAC/B;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,QAAQ,GAAG,SAAS,CAAC;IAC/B,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,eAAe,EAAE,MAAM,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC;CACpB;AAuBD;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAoB1E;AAMD,6HAA6H;AAC7H,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,SAAS,MAAM,EAAE,EAC1B,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,EACzC,OAAO,EAAE,QAAQ,GAAG,SAAS,GAC5B,MAAM,GAAG,IAAI,CAMf;AAkCD,wBAAsB,gBAAgB,CAAC,OAAO,EAAE,eAAe,EAAE,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAAC,kBAAkB,CAAC,CA0J1H"}
@@ -209,18 +209,28 @@ async function importLocalTable(context, options) {
209
209
  if (!uploadId)
210
210
  throw new client_1.CliError('csv_upload_create returned no upload_id.', 'invalid_response', 0, { details: { import_step: 'create' } });
211
211
  const mapping = { ...(record(created.suggested_mapping) ?? {}) };
212
+ const definitions = Array.isArray(created.column_definitions) ? created.column_definitions.map(record) : [];
213
+ if (definitions.length !== file.headers.length || definitions.some((definition) => typeof definition?.source_header !== 'string')) {
214
+ throw new client_1.CliError('csv_upload_create returned invalid column definitions.', 'invalid_response', 0);
215
+ }
216
+ const sourceHeaders = definitions.map((definition) => definition.source_header);
217
+ let upsertHeader;
212
218
  if (options.upsertKey) {
213
219
  if (!file.headers.includes(options.upsertKey)) {
214
220
  throw new errors_1.CliUsageError(`--upsert-key "${options.upsertKey}" is not a column of ${fileName}. Columns: ${file.headers.join(', ')}.`);
215
221
  }
216
- if (!mapping[options.upsertKey]) {
222
+ const matches = file.headers.flatMap((header, index) => header === options.upsertKey ? [index] : []);
223
+ if (matches.length > 1)
224
+ throw new errors_1.CliUsageError(`--upsert-key "${options.upsertKey}" matches multiple columns: ${matches.map((index) => index + 1).join(', ')}. Rename the columns.`);
225
+ upsertHeader = sourceHeaders[matches[0]];
226
+ if (!mapping[upsertHeader]) {
217
227
  if (Object.values(mapping).includes('external_id')) {
218
228
  throw new errors_1.CliUsageError(`--upsert-key "${options.upsertKey}" is not mapped to a field and external_id is already used by another column. Rename the key column to a mappable name (email, linkedin_url, external_id).`);
219
229
  }
220
- mapping[options.upsertKey] = 'external_id';
230
+ mapping[upsertHeader] = 'external_id';
221
231
  }
222
232
  }
223
- const identityColumn = options.upsertKey ?? chooseIdentityColumn(file.headers, mapping, rowKind);
233
+ const identityColumn = upsertHeader ?? chooseIdentityColumn(sourceHeaders, mapping, rowKind);
224
234
  if (!identityColumn || Object.keys(mapping).length === 0) {
225
235
  throw new errors_1.CliUsageError(`No column of ${fileName} could be matched to an identity field (email, linkedin_url, external_id${rowKind === 'company' ? ', domain' : ''}). The upload ${uploadId} was opened and stays unattached. Pass --upsert-key <column> to choose the identity column.`);
226
236
  }
@@ -21,7 +21,18 @@ export declare function reusableTurn(path: string, baseUrl: string, apiKey: stri
21
21
  rows?: number | null;
22
22
  credits?: string | null;
23
23
  } | null>;
24
+ /**
25
+ * The turn token stored for this exact quote_hash. Null when that quote is
26
+ * absent or its fingerprint is not this input: never hand back another quote's token.
27
+ */
28
+ export declare function reusableTurnForQuote(path: string, baseUrl: string, apiKey: string, capabilityId: string, quoteHash: string, fingerprint: string, now?: number, onWarning?: CacheWarning): Promise<{
29
+ token: string;
30
+ quote_hash: string;
31
+ maximum_credits: number | null;
32
+ rows?: number | null;
33
+ credits?: string | null;
34
+ } | null>;
24
35
  export declare function rememberTurnToken(path: string, baseUrl: string, apiKey: string, capabilityId: string, token: string, now?: number, onWarning?: CacheWarning, quote?: CachedQuote): Promise<void>;
25
- export declare function forgetTurnToken(path: string, baseUrl: string, apiKey: string, capabilityId: string, onWarning?: CacheWarning): Promise<void>;
36
+ export declare function forgetTurnToken(path: string, baseUrl: string, apiKey: string, capabilityId: string, onWarning?: CacheWarning, quoteHash?: string): Promise<void>;
26
37
  export {};
27
38
  //# sourceMappingURL=turnCache.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"turnCache.d.ts","sourceRoot":"","sources":["../src/turnCache.ts"],"names":[],"mappings":"AAeA,eAAO,MAAM,aAAa,QAAa,CAAC;AAExC,wPAAwP;AACxP,MAAM,MAAM,WAAW,GAAG;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC;IAAC,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAAC;AAGtJ,KAAK,YAAY,GAAG,CAAC,KAAK,EAAE,MAAM,CAAC,cAAc,KAAK,IAAI,CAAC;AAE3D,wBAAgB,aAAa,CAAC,eAAe,EAAE,MAAM,GAAG,MAAM,CAE7D;AA2BD,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,GAAG,SAAa,EAAE,SAAS,CAAC,EAAE,YAAY,GAC9G,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAGxB;AAED;;;GAGG;AACH,wBAAsB,YAAY,CAChC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,GAAG,SAAa,EAAE,SAAS,CAAC,EAAE,YAAY,GACnI,OAAO,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,GAAG,IAAI,CAAC,CAa7I;AAED,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,SAAa,EAAE,SAAS,CAAC,EAAE,YAAY,EAC9H,KAAK,CAAC,EAAE,WAAW,GAClB,OAAO,CAAC,IAAI,CAAC,CAOf;AAED,wBAAsB,eAAe,CACnC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,YAAY,GAC5F,OAAO,CAAC,IAAI,CAAC,CAMf"}
1
+ {"version":3,"file":"turnCache.d.ts","sourceRoot":"","sources":["../src/turnCache.ts"],"names":[],"mappings":"AAiBA,eAAO,MAAM,aAAa,QAAa,CAAC;AAExC,wPAAwP;AACxP,MAAM,MAAM,WAAW,GAAG;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC;IAAC,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAAC;AAItJ,KAAK,YAAY,GAAG,CAAC,KAAK,EAAE,MAAM,CAAC,cAAc,KAAK,IAAI,CAAC;AAE3D,wBAAgB,aAAa,CAAC,eAAe,EAAE,MAAM,GAAG,MAAM,CAE7D;AAgED,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,GAAG,SAAa,EAAE,SAAS,CAAC,EAAE,YAAY,GAC9G,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAGxB;AAED;;;GAGG;AACH,wBAAsB,YAAY,CAChC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,GAAG,SAAa,EAAE,SAAS,CAAC,EAAE,YAAY,GACnI,OAAO,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,GAAG,IAAI,CAAC,CAU7I;AAED;;;GAGG;AACH,wBAAsB,oBAAoB,CACxC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,GAAG,SAAa,EAAE,SAAS,CAAC,EAAE,YAAY,GACtJ,OAAO,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,GAAG,IAAI,CAAC,CAMtI;AAED,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,SAAa,EAAE,SAAS,CAAC,EAAE,YAAY,EAC9H,KAAK,CAAC,EAAE,WAAW,GAClB,OAAO,CAAC,IAAI,CAAC,CAkBf;AAED,wBAAsB,eAAe,CACnC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,YAAY,EAAE,SAAS,CAAC,EAAE,MAAM,GAChH,OAAO,CAAC,IAAI,CAAC,CAsBf"}
package/dist/turnCache.js CHANGED
@@ -4,6 +4,7 @@ exports.TURN_REUSE_MS = void 0;
4
4
  exports.turnCachePath = turnCachePath;
5
5
  exports.reusableTurnToken = reusableTurnToken;
6
6
  exports.reusableTurn = reusableTurn;
7
+ exports.reusableTurnForQuote = reusableTurnForQuote;
7
8
  exports.rememberTurnToken = rememberTurnToken;
8
9
  exports.forgetTurnToken = forgetTurnToken;
9
10
  const node_crypto_1 = require("node:crypto");
@@ -17,7 +18,9 @@ const node_path_1 = require("node:path");
17
18
  // refused four times in a row). The cache therefore keeps the rehearsal's turn
18
19
  // token together with its quote_hash and a fingerprint of the exact input that
19
20
  // was priced, so a later `--live` of the same input can echo it and a changed
20
- // input never inherits a price it was not shown.
21
+ // input never inherits a price it was not shown. One capability keeps a quote
22
+ // per quote_hash: parallel dry runs of different params must not overwrite
23
+ // each other's token.
21
24
  // The server turn lives 10 minutes; reuse for 9 so a cached token never
22
25
  // expires mid-request.
23
26
  exports.TURN_REUSE_MS = 9 * 60_000;
@@ -49,6 +52,39 @@ async function persist(path, entries, onWarning) {
49
52
  onWarning?.(error);
50
53
  }
51
54
  }
55
+ /** Re-load immediately before persist. `mutate` may change only its own key. */
56
+ async function modifyCache(path, onWarning, mutate) {
57
+ const fresh = await load(path, onWarning);
58
+ if (!mutate(fresh))
59
+ return;
60
+ await persist(path, fresh, onWarning);
61
+ }
62
+ function slots(entry, now) {
63
+ const out = [];
64
+ const seen = new Set();
65
+ for (const slot of Object.values(entry.quotes ?? {})) {
66
+ if (!slot || typeof slot.token !== 'string' || typeof slot.quote?.quote_hash !== 'string')
67
+ continue;
68
+ if (now - slot.at >= exports.TURN_REUSE_MS)
69
+ continue;
70
+ seen.add(slot.quote.quote_hash);
71
+ out.push(slot);
72
+ }
73
+ if (entry.quote?.quote_hash && typeof entry.token === 'string' && now - entry.at < exports.TURN_REUSE_MS && !seen.has(entry.quote.quote_hash)) {
74
+ out.push({ token: entry.token, at: entry.at, quote: entry.quote });
75
+ }
76
+ return out;
77
+ }
78
+ function present(slot) {
79
+ const quote = slot.quote;
80
+ return {
81
+ token: slot.token,
82
+ quote_hash: quote.quote_hash,
83
+ maximum_credits: typeof quote.maximum_credits === 'number' ? quote.maximum_credits : null,
84
+ ...(quote.rows !== undefined ? { rows: quote.rows } : {}),
85
+ ...(quote.credits !== undefined ? { credits: quote.credits } : {}),
86
+ };
87
+ }
52
88
  async function reusableTurnToken(path, baseUrl, apiKey, capabilityId, now = Date.now(), onWarning) {
53
89
  const entry = (await load(path, onWarning))[entryKey(baseUrl, apiKey, capabilityId)];
54
90
  return entry && typeof entry.token === 'string' && now - entry.at < exports.TURN_REUSE_MS ? entry.token : null;
@@ -59,33 +95,76 @@ async function reusableTurnToken(path, baseUrl, apiKey, capabilityId, now = Date
59
95
  */
60
96
  async function reusableTurn(path, baseUrl, apiKey, capabilityId, fingerprint, now = Date.now(), onWarning) {
61
97
  const entry = (await load(path, onWarning))[entryKey(baseUrl, apiKey, capabilityId)];
62
- if (!entry || typeof entry.token !== 'string' || now - entry.at >= exports.TURN_REUSE_MS)
98
+ if (!entry || typeof entry.token !== 'string')
63
99
  return null;
64
- const quote = entry.quote && entry.quote.fingerprint === fingerprint && typeof entry.quote.quote_hash === 'string'
65
- ? entry.quote
66
- : null;
67
- return {
68
- token: entry.token,
69
- quote_hash: quote ? quote.quote_hash : null,
70
- maximum_credits: quote && typeof quote.maximum_credits === 'number' ? quote.maximum_credits : null,
71
- ...(quote && quote.rows !== undefined ? { rows: quote.rows } : {}),
72
- ...(quote && quote.credits !== undefined ? { credits: quote.credits } : {}),
73
- };
100
+ const found = slots(entry, now);
101
+ const match = found.find((slot) => slot.quote.fingerprint === fingerprint);
102
+ if (match)
103
+ return present(match);
104
+ // A quote minted for a different input must not lend its turn token.
105
+ if (found.length > 0)
106
+ return null;
107
+ if (now - entry.at >= exports.TURN_REUSE_MS)
108
+ return null;
109
+ return { token: entry.token, quote_hash: null, maximum_credits: null };
110
+ }
111
+ /**
112
+ * The turn token stored for this exact quote_hash. Null when that quote is
113
+ * absent or its fingerprint is not this input: never hand back another quote's token.
114
+ */
115
+ async function reusableTurnForQuote(path, baseUrl, apiKey, capabilityId, quoteHash, fingerprint, now = Date.now(), onWarning) {
116
+ const entry = (await load(path, onWarning))[entryKey(baseUrl, apiKey, capabilityId)];
117
+ if (!entry)
118
+ return null;
119
+ const match = slots(entry, now).find((slot) => slot.quote.quote_hash === quoteHash);
120
+ if (!match || match.quote.fingerprint !== fingerprint)
121
+ return null;
122
+ return present(match);
74
123
  }
75
124
  async function rememberTurnToken(path, baseUrl, apiKey, capabilityId, token, now = Date.now(), onWarning, quote) {
76
- const entries = await load(path, onWarning);
77
- for (const [key, entry] of Object.entries(entries)) {
78
- if (!entry || now - entry.at >= exports.TURN_REUSE_MS)
79
- delete entries[key];
80
- }
81
- entries[entryKey(baseUrl, apiKey, capabilityId)] = { token, at: now, ...(quote ? { quote } : {}) };
82
- await persist(path, entries, onWarning);
125
+ const key = entryKey(baseUrl, apiKey, capabilityId);
126
+ await modifyCache(path, onWarning, (entries) => {
127
+ for (const [entryKeyName, entry] of Object.entries(entries)) {
128
+ if (!entry || now - entry.at >= exports.TURN_REUSE_MS)
129
+ delete entries[entryKeyName];
130
+ }
131
+ const existing = entries[key];
132
+ const quotes = {};
133
+ for (const slot of slots(existing ?? { token, at: now }, now))
134
+ quotes[slot.quote.quote_hash] = slot;
135
+ if (quote?.quote_hash)
136
+ quotes[quote.quote_hash] = { token, at: now, quote };
137
+ entries[key] = {
138
+ token,
139
+ at: now,
140
+ ...(quote ? { quote } : existing?.quote ? { quote: existing.quote } : {}),
141
+ ...(Object.keys(quotes).length > 0 ? { quotes } : {}),
142
+ };
143
+ return true;
144
+ });
83
145
  }
84
- async function forgetTurnToken(path, baseUrl, apiKey, capabilityId, onWarning) {
85
- const entries = await load(path, onWarning);
146
+ async function forgetTurnToken(path, baseUrl, apiKey, capabilityId, onWarning, quoteHash) {
86
147
  const key = entryKey(baseUrl, apiKey, capabilityId);
87
- if (!(key in entries))
88
- return;
89
- delete entries[key];
90
- await persist(path, entries, onWarning);
148
+ await modifyCache(path, onWarning, (entries) => {
149
+ const entry = entries[key];
150
+ if (!entry)
151
+ return false;
152
+ if (!quoteHash) {
153
+ delete entries[key];
154
+ return true;
155
+ }
156
+ const quotes = {};
157
+ for (const slot of slots(entry, Date.now())) {
158
+ if (slot.quote.quote_hash !== quoteHash)
159
+ quotes[slot.quote.quote_hash] = slot;
160
+ }
161
+ const remaining = Object.values(quotes);
162
+ if (remaining.length === 0) {
163
+ delete entries[key];
164
+ return true;
165
+ }
166
+ const kept = remaining[remaining.length - 1];
167
+ entries[key] = { token: kept.token, at: kept.at, quote: kept.quote, quotes };
168
+ return true;
169
+ });
91
170
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trydreamstate/cli",
3
- "version": "1.3.13",
3
+ "version": "1.3.15",
4
4
  "description": "Live command-line client for the Dreamstate v1 API",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",