dsh-openwrite 0.2.19 → 0.2.20

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 (41) hide show
  1. package/README.md +20 -5
  2. package/docs/ISSUE_76_REPAIRS.md +61 -0
  3. package/docs/RELEASE_ACCEPTANCE.md +1 -1
  4. package/package.json +1 -1
  5. package/packages/openwrite-bridge/lib/client.d.ts +1 -0
  6. package/packages/openwrite-bridge/lib/client.js +26 -4
  7. package/packages/openwrite-bridge/lib/contracts-generated.d.ts +1 -1
  8. package/packages/openwrite-bridge/lib/contracts-generated.js +1 -2
  9. package/packages/openwrite-bridge/lib/domain.js +2 -0
  10. package/packages/openwrite-bridge/lib/index.js +6 -6
  11. package/packages/openwrite-bridge/lib/managed-runtime.js +37 -37
  12. package/packages/openwrite-bridge/lib/tools.js +67 -31
  13. package/packages/studio-panel/lib/client.js +478 -478
  14. package/presets/openwrite/agent.cordis.yml +10 -2
  15. package/presets/openwrite/skills/canon/SKILL.md +2 -0
  16. package/presets/openwrite/skills/dialoguequality/SKILL.md +2 -0
  17. package/presets/openwrite/skills/dog-delivery-query/SKILL.md +2 -0
  18. package/presets/openwrite/skills/dog-import-query/SKILL.md +2 -0
  19. package/presets/openwrite/skills/dog-review-query/SKILL.md +2 -0
  20. package/presets/openwrite/skills/export-book/SKILL.md +2 -0
  21. package/presets/openwrite/skills/foreshadow/SKILL.md +2 -0
  22. package/presets/openwrite/skills/foreshadowing-system/SKILL.md +2 -0
  23. package/presets/openwrite/skills/learn/SKILL.md +2 -0
  24. package/presets/openwrite/skills/novel-creator/SKILL.md +2 -0
  25. package/presets/openwrite/skills/novel-reviewer/SKILL.md +2 -0
  26. package/presets/openwrite/skills/oh-story-deslop/SKILL.md +2 -0
  27. package/presets/openwrite/skills/oh-story-long-analyze/SKILL.md +2 -0
  28. package/presets/openwrite/skills/oh-story-long-scan/SKILL.md +2 -0
  29. package/presets/openwrite/skills/oh-story-long-write/SKILL.md +2 -0
  30. package/presets/openwrite/skills/oh-story-review/SKILL.md +2 -0
  31. package/presets/openwrite/skills/progress/SKILL.md +2 -0
  32. package/presets/openwrite/skills/review-chapter/SKILL.md +2 -0
  33. package/presets/openwrite/skills/revise-span/SKILL.md +2 -0
  34. package/presets/openwrite/skills/style-system/SKILL.md +2 -0
  35. package/presets/openwrite/skills/truth-validation/SKILL.md +2 -0
  36. package/presets/openwrite/skills/workflow-manager/SKILL.md +2 -0
  37. package/presets/openwrite/skills/world-query/SKILL.md +2 -0
  38. package/presets/openwrite/skills/write-next/SKILL.md +2 -0
  39. package/release/{openwrite-5.8.9-py3-none-any.whl → openwrite-5.8.10-py3-none-any.whl} +0 -0
  40. package/release/runtime-manifest.json +5 -5
  41. package/release/source.json +3 -3
@@ -124,7 +124,18 @@ function defineTool(options) {
124
124
  const violations = validate(normalized);
125
125
  if (violations.length > 0)
126
126
  throw new ToolArgsError(violations);
127
- return userExecute(normalized, exec);
127
+ try {
128
+ return await userExecute(normalized, exec);
129
+ }
130
+ catch (error) {
131
+ if (!(error instanceof StudioError) || error.code.startsWith('WORKSPACE_'))
132
+ throw error;
133
+ error.message = JSON.stringify({ code: error.code, message: error.message,
134
+ status: error.status, details: error.details, request_id: error.requestId,
135
+ recovery: error.code === 'NETWORK_ERROR' ? 'List recent tasks and chapter runs before resubmitting; the server may have accepted the request.' : null,
136
+ });
137
+ throw error;
138
+ }
128
139
  },
129
140
  };
130
141
  if (userFinalizeContent)
@@ -518,9 +529,14 @@ export function registerNovelTools(ctx, clientFactory, options) {
518
529
  'rejects the edit with a conflict if the outline changed in between. ' +
519
530
  'Operations: "rename" (node_id + title), "update_summary" (node_id + summary), ' +
520
531
  '"add_child" (node_id = parent, + title/summary/kind), "add_after" (node_id = sibling, + title/summary/kind), ' +
521
- '"delete" (node_id).',
522
- parameters: {
523
- operation: { type: 'string', required: true, enum: ['rename', 'update_summary', 'add_child', 'add_after', 'delete'], description: 'The structural operation to apply.' },
532
+ '"delete" (node_id, cascade=false keeps children), "promote" (remove a container), and ' +
533
+ '"get_meta"/"set_meta" (chapter metadata, optionally updates[] for a batch).',
534
+ parameters: {
535
+ operation: { type: 'string', required: true, enum: ['rename', 'update_summary', 'add_child', 'add_after', 'delete', 'promote', 'get_meta', 'set_meta'], description: 'The structural operation to apply.' },
536
+ cascade: { type: 'boolean', description: 'For delete, false removes only the container and keeps children and chapter IDs.' },
537
+ metadata: { type: 'json', description: 'Field object for set_meta; use target_words, beats, hooks or characters.' },
538
+ chapter_id: { type: 'string', description: 'Chapter ID alias for node_id in get_meta/set_meta.' },
539
+ updates: { type: 'json', description: 'Batch of 1-50 {chapter_id, metadata} objects for set_meta, or {chapter_id} for get_meta; one revision guards the whole batch.' },
524
540
  revision: { type: 'string', required: true, description: 'The outline revision fingerprint from a fresh novel_outline_read call.' },
525
541
  node_id: { type: 'string', description: 'Target node id (parent for add_child, sibling for add_after); not needed for add_child at root.' },
526
542
  title: { type: 'string', description: 'Node title (rename / add_child / add_after).' },
@@ -538,6 +554,14 @@ export function registerNovelTools(ctx, clientFactory, options) {
538
554
  body['title'] = args.title;
539
555
  if (args.summary !== undefined)
540
556
  body['summary'] = args.summary;
557
+ if (args.cascade !== undefined)
558
+ body['cascade'] = args.cascade;
559
+ if (args.metadata !== undefined)
560
+ body['metadata'] = args.metadata;
561
+ if (args.chapter_id !== undefined)
562
+ body['chapter_id'] = args.chapter_id;
563
+ if (args.updates !== undefined)
564
+ body['updates'] = args.updates;
541
565
  if (args.kind !== undefined)
542
566
  body['kind'] = args.kind;
543
567
  return await client.postJson('/api/outline/edit', body, exec.signal);
@@ -671,11 +695,13 @@ export function registerNovelTools(ctx, clientFactory, options) {
671
695
  }));
672
696
  ctx.tools.register(defineTool({
673
697
  name: 'novel_write_chapter',
674
- description: 'Write a chapter with OpenWrite (LONG-RUNNING, may take several minutes). Omit chapter_id to write ' +
698
+ description: 'Queue a chapter write and immediately return a durable task_id. Poll novel_task_get for progress/result. Omit chapter_id to write ' +
675
699
  'the outline-recommended next chapter; a given chapter_id must match the current recommendation. ' +
676
700
  'Preview context first with novel_context_preview and consult novel_outline_read for the recommendation.',
677
701
  parameters: {
678
- target_words: { type: 'integer', description: 'Target word count, 200-12000 (default 3000).' },
702
+ run_id_v2: { type: 'string', description: 'Resume this completed draft without generating it again.' },
703
+ resume_from_draft: { type: 'boolean', description: 'Resume the latest uncommitted draft for this chapter.' },
704
+ target_words: { type: 'integer', description: 'Target word count, 200-50000. When omitted use the outline target, then default 3000.' },
679
705
  guidance: { type: 'string', description: 'Writing guidance for this chapter (plot beats, tone, POV notes).' },
680
706
  chapter_id: { type: 'string', description: 'Chapter id to write; must equal the current outline recommendation. Omit for the next chapter.' },
681
707
  temperature: { type: 'number', description: 'Sampling temperature (default 0.7).' },
@@ -685,11 +711,17 @@ export function registerNovelTools(ctx, clientFactory, options) {
685
711
  timeoutMs,
686
712
  async execute(args, exec) {
687
713
  const client = clientFor(exec);
688
- const targetWords = args.target_words ?? 3000;
689
- if (!Number.isInteger(targetWords) || targetWords < 200 || targetWords > 12000) {
690
- throw new Error(`target_words must be an integer between 200 and 12000, got ${String(args.target_words)}`);
714
+ const targetWords = args.target_words;
715
+ if (targetWords !== undefined && (!Number.isInteger(targetWords) || targetWords < 200 || targetWords > 50000)) {
716
+ throw new Error(`target_words must be an integer between 200 and 50000, got ${String(args.target_words)}`);
691
717
  }
692
- const body = { target_words: targetWords, guidance: args.guidance ?? '' };
718
+ const body = { guidance: args.guidance ?? '' };
719
+ if (targetWords !== undefined)
720
+ body['target_words'] = targetWords;
721
+ if (args.run_id_v2 !== undefined)
722
+ body['run_id_v2'] = args.run_id_v2;
723
+ if (args.resume_from_draft !== undefined)
724
+ body['resume_from_draft'] = args.resume_from_draft;
693
725
  const chapterId = args.chapter_id?.trim();
694
726
  if (chapterId)
695
727
  body['chapter_id'] = chapterId;
@@ -697,17 +729,7 @@ export function registerNovelTools(ctx, clientFactory, options) {
697
729
  body['temperature'] = args.temperature;
698
730
  if (args.outline_revision !== undefined)
699
731
  body['outline_revision'] = args.outline_revision;
700
- const response = await client.postJson('/api/write', body, exec.signal);
701
- const writeValue = objectValue(response);
702
- const writeResult = objectValue(writeValue['result'] ?? response);
703
- const writtenChapterId = chapterId || String(writeResult['chapter_id'] ?? '');
704
- try {
705
- const dogDelivery = await deliveryForResponse(client, response, writtenChapterId, exec.signal);
706
- return { ...objectValue(response), dog_delivery: dogDelivery };
707
- }
708
- catch (error) {
709
- return { ...objectValue(response), dog_delivery: { status: 'unavailable', error: error instanceof Error ? error.message : String(error) } };
710
- }
732
+ return await client.postJson('/api/tasks', { type: 'chapter_write', input: body }, exec.signal);
711
733
  },
712
734
  }));
713
735
  ctx.tools.register(defineTool({
@@ -715,6 +737,8 @@ export function registerNovelTools(ctx, clientFactory, options) {
715
737
  description: 'Run OpenWrite\'s multi-dimensional review on a drafted manuscript chapter (LONG-RUNNING). ' +
716
738
  'Pass either the manuscript path (e.g. "data/manuscript/ch_0007.md") or a chapter_id like "ch_0007".',
717
739
  parameters: {
740
+ action: { type: 'string', enum: ['review', 'versions', 'compare'], description: 'Default review; versions lists saved reviews, compare compares a historical revision with the current review.' },
741
+ revision: { type: 'string', description: 'Historical review revision for compare.' },
718
742
  path: { type: 'string', description: 'Manuscript path relative to the novel data root. Takes precedence over chapter_id.' },
719
743
  chapter_id: { type: 'string', description: 'Chapter id (ch_<digits>), resolved client-side to data/manuscript/<id>.md.' },
720
744
  strict: { type: 'boolean', description: 'Enable strict review mode (default false).' },
@@ -725,6 +749,8 @@ export function registerNovelTools(ctx, clientFactory, options) {
725
749
  timeoutMs,
726
750
  async execute(args, exec) {
727
751
  const client = clientFor(exec);
752
+ if (args.action === 'versions' || args.action === 'compare')
753
+ return await client.postJson('/api/review/history', { action: args.action, chapter_id: reviewChapterId(args), revision: args.revision ?? '' }, exec.signal);
728
754
  if (args.dimensions !== undefined && args.dimensions.some(value => !Number.isInteger(value) || value < 1 || value > 37)) {
729
755
  throw new Error('dimensions must contain only integers from 1 to 37');
730
756
  }
@@ -850,11 +876,12 @@ export function registerNovelTools(ctx, clientFactory, options) {
850
876
  name: 'novel_document_change_plan',
851
877
  description: 'Stage and review revision-gated changes to an existing novel document. Use action "preview" with path and edits ' +
852
878
  'to receive an immutable preview_token and real before/after summary. Use "apply" only after author acceptance, ' +
853
- 'passing only that token; "reject" discards it, "retry" rebuilds it against the current source, and "undo" safely ' +
879
+ 'passing that token and confirm: true; "reject" discards it, "retry" rebuilds it against the current source, and "undo" safely ' +
854
880
  'applies an undo_preview_token returned by a prior apply. Each edit uses old_text/new_text or unique start_text/' +
855
881
  'end_text/new_text anchors. The server rechecks the source revision and writes exactly the stored preview.',
856
882
  parameters: {
857
883
  action: { type: 'string', required: true, enum: ['preview', 'apply', 'reject', 'retry', 'undo'], description: 'Plan lifecycle action.' },
884
+ confirm: { type: 'boolean', description: 'Required true for apply and undo after author acceptance.' },
858
885
  path: { type: 'string', description: 'Existing document path for preview, relative to the novel root.' },
859
886
  edits: { type: 'json', description: 'Preview edits: [{old_text,new_text}] or [{start_text,end_text,new_text}].' },
860
887
  preview_token: { type: 'string', description: 'Immutable preview or undo token for apply/reject/retry/undo.' },
@@ -862,15 +889,19 @@ export function registerNovelTools(ctx, clientFactory, options) {
862
889
  output: { schema: JSON_OUTPUT_SCHEMA, render: (_args, value) => renderJson(value) },
863
890
  timeoutMs,
864
891
  async execute(args, exec) {
865
- if (args.action === 'preview' && (!Array.isArray(args.edits) || args.edits.length !== 1)) {
866
- throw new Error('preview requires exactly one edit so the author can accept or reject each change independently ' +
892
+ if (args.action === 'preview' && (!Array.isArray(args.edits) || (args.edits.length < 1 || args.edits.length > 50))) {
893
+ throw new Error('preview requires between 1 and 50 edits ' +
867
894
  `(received ${describeValue(args.edits)})`);
868
895
  }
869
896
  if (args.action !== 'preview' && !args.preview_token?.trim()) {
870
897
  throw new Error(`${args.action} requires preview_token`);
871
898
  }
899
+ if (['apply', 'undo'].includes(args.action) && args.confirm !== true)
900
+ throw new Error('apply/undo requires confirm: true');
872
901
  const client = clientFor(exec);
873
902
  const body = { action: args.action };
903
+ if (args.confirm !== undefined)
904
+ body['confirm'] = args.confirm;
874
905
  if (args.path !== undefined)
875
906
  body['path'] = args.path;
876
907
  if (args.edits !== undefined)
@@ -1319,6 +1350,7 @@ export function registerNovelTools(ctx, clientFactory, options) {
1319
1350
  name: 'novel_tasks_list',
1320
1351
  description: 'List background tasks (queued/running/finished AI jobs) with per-status counts.',
1321
1352
  parameters: {
1353
+ since: { type: 'string', description: 'ISO timestamp filter for requests whose response was lost.' },
1322
1354
  limit: { type: 'integer', description: 'Maximum number of tasks (server default 100).' },
1323
1355
  },
1324
1356
  output: { schema: JSON_OUTPUT_SCHEMA, render: (_args, value) => renderJson(value) },
@@ -1327,6 +1359,8 @@ export function registerNovelTools(ctx, clientFactory, options) {
1327
1359
  async execute(args, exec) {
1328
1360
  const client = clientFor(exec);
1329
1361
  const params = {};
1362
+ if (args.since !== undefined)
1363
+ params['since'] = args.since;
1330
1364
  if (args.limit !== undefined)
1331
1365
  params['limit'] = String(args.limit);
1332
1366
  return await client.getJson('/api/tasks', params, exec.signal);
@@ -1430,8 +1464,9 @@ export function registerNovelTools(ctx, clientFactory, options) {
1430
1464
  }));
1431
1465
  ctx.tools.register(defineTool({
1432
1466
  name: 'novel_task_cancel',
1433
- description: 'Cancel a queued or running background task. Cancellation is permanent for that task; retry with novel_task_retry if the type supports it.',
1467
+ description: 'Request cancellation of a queued or running task. Check cancelled and cancellation_status for the actual outcome; poll novel_task_get while cancellation is requested.',
1434
1468
  parameters: {
1469
+ wait_seconds: { type: 'number', description: 'Wait up to 30 seconds for actual cancellation; running model calls may finish later.' },
1435
1470
  task_id: { type: 'string', required: true, description: 'Task id (tsk_*).' },
1436
1471
  },
1437
1472
  output: { schema: JSON_OUTPUT_SCHEMA, render: (_args, value) => renderJson(value) },
@@ -1440,7 +1475,7 @@ export function registerNovelTools(ctx, clientFactory, options) {
1440
1475
  const client = clientFor(exec);
1441
1476
  if (!/^tsk_[A-Za-z0-9_-]+$/.test(args.task_id))
1442
1477
  throw new Error(`invalid task_id "${args.task_id}"`);
1443
- return await client.postJson(`/api/tasks/${encodeURIComponent(args.task_id)}/cancel`, {}, exec.signal);
1478
+ return await client.postJson(`/api/tasks/${encodeURIComponent(args.task_id)}/cancel`, { wait_seconds: args.wait_seconds ?? 0 }, exec.signal);
1444
1479
  },
1445
1480
  }));
1446
1481
  ctx.tools.register(defineTool({
@@ -1910,7 +1945,7 @@ export function registerNovelTools(ctx, clientFactory, options) {
1910
1945
  ctx.tools.register(defineTool({
1911
1946
  name: 'novel_writing_targets',
1912
1947
  description: 'Immediate compatibility write for targets the author has already authorized exactly; otherwise use ' +
1913
- 'novel_structured_change_plan. Update the project writing targets. Ranges: book_words 10000-5000000, chapter_words 200-12000, ' +
1948
+ 'novel_structured_change_plan. Update the project writing targets. Ranges: book_words 10000-5000000, chapter_words 200-50000, ' +
1914
1949
  'outline_volume_words 100-5000, outline_act_words 80-3000, outline_section_words 50-2000, ' +
1915
1950
  'outline_chapter_words 30-1000. Omitted fields keep their current values.',
1916
1951
  parameters: {
@@ -1983,11 +2018,12 @@ export function registerNovelTools(ctx, clientFactory, options) {
1983
2018
  'confirm: true), "cancel" (run_id + revision, optional reason). All mutating actions are revision-gated: ' +
1984
2019
  'pass the run\'s current revision from a list/get call.',
1985
2020
  parameters: {
1986
- action: { type: 'string', required: true, enum: ['list', 'get', 'record_intervention', 'update_intervention', 'cancel'], description: 'The chapter-run operation.' },
2021
+ action: { type: 'string', required: true, enum: ['list', 'get', 'record_intervention', 'update_intervention', 'cancel', 'archive'], description: 'The chapter-run operation.' },
1987
2022
  run_id: { type: 'string', description: 'Run id (required for get/record_intervention/update_intervention/cancel).' },
1988
2023
  revision: { type: 'string', description: 'Current run revision (required for mutating actions).' },
1989
2024
  chapter_id: { type: 'string', description: 'Chapter filter for list.' },
1990
2025
  statuses: { type: 'array', items: { type: 'string' }, description: 'Status filter for list.' },
2026
+ since: { type: 'string', description: 'ISO timestamp filter for recent runs.' },
1991
2027
  limit: { type: 'integer', description: 'List limit (default 20).' },
1992
2028
  scope: { type: 'string', description: 'Intervention scope (default "chapter").' },
1993
2029
  risk: { type: 'string', description: 'Intervention risk level (default "medium").' },
@@ -2008,7 +2044,7 @@ export function registerNovelTools(ctx, clientFactory, options) {
2008
2044
  async execute(args, exec) {
2009
2045
  const client = clientFor(exec);
2010
2046
  const body = { action: args.action };
2011
- for (const key of ['run_id', 'revision', 'chapter_id', 'statuses', 'limit', 'scope', 'risk', 'request', 'affected_items', 'rewrite_required', 'intervention_id', 'state', 'facts_revision', 'impact', 'proposal', 'confirm', 'reason']) {
2047
+ for (const key of ['run_id', 'revision', 'chapter_id', 'statuses', 'since', 'limit', 'scope', 'risk', 'request', 'affected_items', 'rewrite_required', 'intervention_id', 'state', 'facts_revision', 'impact', 'proposal', 'confirm', 'reason']) {
2012
2048
  const value = args[key];
2013
2049
  if (value !== undefined)
2014
2050
  body[key] = value;
@@ -2122,11 +2158,11 @@ export function registerNovelTools(ctx, clientFactory, options) {
2122
2158
  name: 'novel_manuscript_acceptance_reconcile',
2123
2159
  description: 'Reconcile the canonical manuscript acceptance ledger. Actions: "baseline" (accept current text as the ' +
2124
2160
  'initial baseline), "external" (accept a detected external edit), "resume" (continue a durable incomplete ' +
2125
- 'operation), and "acknowledge" (record that the author reviewed the completed acceptance and impacts). ' +
2161
+ 'operation), "abandon" (release a stale operation without deleting prose), and "acknowledge" (record that the author reviewed the completed acceptance and impacts). ' +
2126
2162
  'Each action is mapped to the corresponding OpenWrite acceptance endpoint. Baseline, external, and ' +
2127
2163
  'acknowledge are confirmation-gated by the server.',
2128
2164
  parameters: {
2129
- action: { type: 'string', required: true, enum: ['baseline', 'external', 'resume', 'acknowledge'], description: 'The reconciliation action.' },
2165
+ action: { type: 'string', required: true, enum: ['baseline', 'external', 'resume', 'acknowledge', 'abandon'], description: 'The reconciliation action.' },
2130
2166
  chapter_id: { type: 'string', description: 'Optional chapter id (ch_<digits>); omit for the project-level operation.' },
2131
2167
  operation_id: { type: 'string', description: 'Durable operation id to resume or acknowledge.' },
2132
2168
  domains: { type: 'array', items: { type: 'string' }, description: 'Affected domains to acknowledge or retry; acknowledge defaults to outline and foreshadowing.' },