astrocode-workflow 0.0.1

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 (133) hide show
  1. package/LICENSE +1 -0
  2. package/README.md +85 -0
  3. package/dist/agents/commands.d.ts +9 -0
  4. package/dist/agents/commands.js +121 -0
  5. package/dist/agents/prompts.d.ts +2 -0
  6. package/dist/agents/prompts.js +27 -0
  7. package/dist/agents/registry.d.ts +6 -0
  8. package/dist/agents/registry.js +223 -0
  9. package/dist/agents/types.d.ts +14 -0
  10. package/dist/agents/types.js +8 -0
  11. package/dist/config/config-handler.d.ts +4 -0
  12. package/dist/config/config-handler.js +46 -0
  13. package/dist/config/defaults.d.ts +3 -0
  14. package/dist/config/defaults.js +3 -0
  15. package/dist/config/loader.d.ts +11 -0
  16. package/dist/config/loader.js +48 -0
  17. package/dist/config/schema.d.ts +176 -0
  18. package/dist/config/schema.js +198 -0
  19. package/dist/hooks/continuation-enforcer.d.ts +26 -0
  20. package/dist/hooks/continuation-enforcer.js +166 -0
  21. package/dist/hooks/tool-output-truncator.d.ts +17 -0
  22. package/dist/hooks/tool-output-truncator.js +56 -0
  23. package/dist/index.d.ts +3 -0
  24. package/dist/index.js +108 -0
  25. package/dist/shared/deep-merge.d.ts +8 -0
  26. package/dist/shared/deep-merge.js +25 -0
  27. package/dist/shared/hash.d.ts +1 -0
  28. package/dist/shared/hash.js +4 -0
  29. package/dist/shared/log.d.ts +7 -0
  30. package/dist/shared/log.js +24 -0
  31. package/dist/shared/model-tuning.d.ts +9 -0
  32. package/dist/shared/model-tuning.js +28 -0
  33. package/dist/shared/paths.d.ts +19 -0
  34. package/dist/shared/paths.js +51 -0
  35. package/dist/shared/text.d.ts +4 -0
  36. package/dist/shared/text.js +19 -0
  37. package/dist/shared/time.d.ts +1 -0
  38. package/dist/shared/time.js +3 -0
  39. package/dist/state/adapters/index.d.ts +39 -0
  40. package/dist/state/adapters/index.js +119 -0
  41. package/dist/state/db.d.ts +17 -0
  42. package/dist/state/db.js +83 -0
  43. package/dist/state/ids.d.ts +8 -0
  44. package/dist/state/ids.js +25 -0
  45. package/dist/state/schema.d.ts +2 -0
  46. package/dist/state/schema.js +247 -0
  47. package/dist/state/types.d.ts +70 -0
  48. package/dist/state/types.js +1 -0
  49. package/dist/tools/artifacts.d.ts +18 -0
  50. package/dist/tools/artifacts.js +71 -0
  51. package/dist/tools/index.d.ts +8 -0
  52. package/dist/tools/index.js +100 -0
  53. package/dist/tools/init.d.ts +8 -0
  54. package/dist/tools/init.js +41 -0
  55. package/dist/tools/injects.d.ts +23 -0
  56. package/dist/tools/injects.js +99 -0
  57. package/dist/tools/repair.d.ts +8 -0
  58. package/dist/tools/repair.js +25 -0
  59. package/dist/tools/run.d.ts +13 -0
  60. package/dist/tools/run.js +54 -0
  61. package/dist/tools/spec.d.ts +13 -0
  62. package/dist/tools/spec.js +41 -0
  63. package/dist/tools/stage.d.ts +23 -0
  64. package/dist/tools/stage.js +284 -0
  65. package/dist/tools/status.d.ts +8 -0
  66. package/dist/tools/status.js +107 -0
  67. package/dist/tools/story.d.ts +23 -0
  68. package/dist/tools/story.js +85 -0
  69. package/dist/tools/workflow.d.ts +8 -0
  70. package/dist/tools/workflow.js +197 -0
  71. package/dist/ui/inject.d.ts +5 -0
  72. package/dist/ui/inject.js +9 -0
  73. package/dist/ui/toasts.d.ts +13 -0
  74. package/dist/ui/toasts.js +39 -0
  75. package/dist/workflow/artifacts.d.ts +24 -0
  76. package/dist/workflow/artifacts.js +45 -0
  77. package/dist/workflow/baton.d.ts +66 -0
  78. package/dist/workflow/baton.js +101 -0
  79. package/dist/workflow/context.d.ts +12 -0
  80. package/dist/workflow/context.js +67 -0
  81. package/dist/workflow/directives.d.ts +37 -0
  82. package/dist/workflow/directives.js +111 -0
  83. package/dist/workflow/repair.d.ts +8 -0
  84. package/dist/workflow/repair.js +99 -0
  85. package/dist/workflow/state-machine.d.ts +43 -0
  86. package/dist/workflow/state-machine.js +127 -0
  87. package/dist/workflow/story-helpers.d.ts +9 -0
  88. package/dist/workflow/story-helpers.js +13 -0
  89. package/package.json +32 -0
  90. package/src/agents/commands.ts +137 -0
  91. package/src/agents/prompts.ts +28 -0
  92. package/src/agents/registry.ts +310 -0
  93. package/src/agents/types.ts +31 -0
  94. package/src/config/config-handler.ts +48 -0
  95. package/src/config/defaults.ts +4 -0
  96. package/src/config/loader.ts +55 -0
  97. package/src/config/schema.ts +236 -0
  98. package/src/hooks/continuation-enforcer.ts +217 -0
  99. package/src/hooks/tool-output-truncator.ts +82 -0
  100. package/src/index.ts +131 -0
  101. package/src/shared/deep-merge.ts +28 -0
  102. package/src/shared/hash.ts +5 -0
  103. package/src/shared/log.ts +30 -0
  104. package/src/shared/model-tuning.ts +48 -0
  105. package/src/shared/paths.ts +70 -0
  106. package/src/shared/text.ts +20 -0
  107. package/src/shared/time.ts +3 -0
  108. package/src/shims.node.d.ts +20 -0
  109. package/src/state/adapters/index.ts +155 -0
  110. package/src/state/db.ts +105 -0
  111. package/src/state/ids.ts +33 -0
  112. package/src/state/schema.ts +249 -0
  113. package/src/state/types.ts +76 -0
  114. package/src/tools/artifacts.ts +83 -0
  115. package/src/tools/index.ts +111 -0
  116. package/src/tools/init.ts +50 -0
  117. package/src/tools/injects.ts +108 -0
  118. package/src/tools/repair.ts +31 -0
  119. package/src/tools/run.ts +62 -0
  120. package/src/tools/spec.ts +50 -0
  121. package/src/tools/stage.ts +361 -0
  122. package/src/tools/status.ts +119 -0
  123. package/src/tools/story.ts +106 -0
  124. package/src/tools/workflow.ts +241 -0
  125. package/src/ui/inject.ts +13 -0
  126. package/src/ui/toasts.ts +48 -0
  127. package/src/workflow/artifacts.ts +69 -0
  128. package/src/workflow/baton.ts +141 -0
  129. package/src/workflow/context.ts +86 -0
  130. package/src/workflow/directives.ts +170 -0
  131. package/src/workflow/repair.ts +138 -0
  132. package/src/workflow/state-machine.ts +194 -0
  133. package/src/workflow/story-helpers.ts +18 -0
@@ -0,0 +1,176 @@
1
+ import { z } from "zod";
2
+ export declare const PermissionValueSchema: z.ZodEnum<{
3
+ ask: "ask";
4
+ allow: "allow";
5
+ deny: "deny";
6
+ }>;
7
+ export type PermissionValue = z.infer<typeof PermissionValueSchema>;
8
+ declare const StageKeySchema: z.ZodEnum<{
9
+ frame: "frame";
10
+ plan: "plan";
11
+ spec: "spec";
12
+ implement: "implement";
13
+ review: "review";
14
+ verify: "verify";
15
+ close: "close";
16
+ }>;
17
+ export type StageKey = z.infer<typeof StageKeySchema>;
18
+ export declare const AstrocodeConfigSchema: z.ZodDefault<z.ZodObject<{
19
+ disabled_hooks: z.ZodOptional<z.ZodDefault<z.ZodArray<z.ZodString>>>;
20
+ disabled_agents: z.ZodOptional<z.ZodDefault<z.ZodArray<z.ZodString>>>;
21
+ disabled_commands: z.ZodOptional<z.ZodDefault<z.ZodArray<z.ZodString>>>;
22
+ determinism: z.ZodOptional<z.ZodDefault<z.ZodObject<{
23
+ mode: z.ZodOptional<z.ZodDefault<z.ZodEnum<{
24
+ on: "on";
25
+ off: "off";
26
+ }>>>;
27
+ strict_stage_order: z.ZodOptional<z.ZodDefault<z.ZodBoolean>>;
28
+ }, z.core.$strip>>>;
29
+ db: z.ZodOptional<z.ZodDefault<z.ZodObject<{
30
+ path: z.ZodOptional<z.ZodDefault<z.ZodString>>;
31
+ busy_timeout_ms: z.ZodOptional<z.ZodDefault<z.ZodNumber>>;
32
+ pragmas: z.ZodOptional<z.ZodDefault<z.ZodObject<{
33
+ journal_mode: z.ZodOptional<z.ZodDefault<z.ZodEnum<{
34
+ WAL: "WAL";
35
+ DELETE: "DELETE";
36
+ }>>>;
37
+ synchronous: z.ZodOptional<z.ZodDefault<z.ZodEnum<{
38
+ NORMAL: "NORMAL";
39
+ FULL: "FULL";
40
+ OFF: "OFF";
41
+ }>>>;
42
+ foreign_keys: z.ZodOptional<z.ZodDefault<z.ZodBoolean>>;
43
+ temp_store: z.ZodOptional<z.ZodDefault<z.ZodEnum<{
44
+ DEFAULT: "DEFAULT";
45
+ MEMORY: "MEMORY";
46
+ FILE: "FILE";
47
+ }>>>;
48
+ }, z.core.$strip>>>;
49
+ schema_version_required: z.ZodOptional<z.ZodDefault<z.ZodNumber>>;
50
+ allow_auto_migrate: z.ZodOptional<z.ZodDefault<z.ZodBoolean>>;
51
+ fail_on_downgrade: z.ZodOptional<z.ZodDefault<z.ZodBoolean>>;
52
+ }, z.core.$strip>>>;
53
+ workflow: z.ZodOptional<z.ZodDefault<z.ZodObject<{
54
+ pipeline: z.ZodOptional<z.ZodDefault<z.ZodArray<z.ZodEnum<{
55
+ frame: "frame";
56
+ plan: "plan";
57
+ spec: "spec";
58
+ implement: "implement";
59
+ review: "review";
60
+ verify: "verify";
61
+ close: "close";
62
+ }>>>>;
63
+ default_mode: z.ZodOptional<z.ZodDefault<z.ZodEnum<{
64
+ step: "step";
65
+ loop: "loop";
66
+ }>>>;
67
+ default_max_steps: z.ZodOptional<z.ZodDefault<z.ZodNumber>>;
68
+ loop_max_steps_hard_cap: z.ZodOptional<z.ZodDefault<z.ZodNumber>>;
69
+ plan_max_tasks: z.ZodOptional<z.ZodDefault<z.ZodNumber>>;
70
+ plan_max_lines: z.ZodOptional<z.ZodDefault<z.ZodNumber>>;
71
+ forbid_prompt_narration: z.ZodOptional<z.ZodDefault<z.ZodBoolean>>;
72
+ single_active_run_per_repo: z.ZodOptional<z.ZodDefault<z.ZodBoolean>>;
73
+ lock_timeout_ms: z.ZodOptional<z.ZodDefault<z.ZodNumber>>;
74
+ role_first_subagents: z.ZodOptional<z.ZodDefault<z.ZodBoolean>>;
75
+ evidence_required: z.ZodOptional<z.ZodDefault<z.ZodObject<{
76
+ verify: z.ZodOptional<z.ZodDefault<z.ZodBoolean>>;
77
+ implement: z.ZodOptional<z.ZodDefault<z.ZodBoolean>>;
78
+ }, z.core.$strip>>>;
79
+ }, z.core.$strip>>>;
80
+ continuation: z.ZodOptional<z.ZodDefault<z.ZodObject<{
81
+ enabled: z.ZodOptional<z.ZodDefault<z.ZodBoolean>>;
82
+ injection_mode: z.ZodOptional<z.ZodDefault<z.ZodEnum<{
83
+ visible: "visible";
84
+ silent: "silent";
85
+ }>>>;
86
+ inject_on_session_idle: z.ZodOptional<z.ZodDefault<z.ZodBoolean>>;
87
+ session_idle_ms: z.ZodOptional<z.ZodDefault<z.ZodNumber>>;
88
+ inject_on_tool_done_if_run_active: z.ZodOptional<z.ZodDefault<z.ZodBoolean>>;
89
+ inject_on_message_done_if_run_active: z.ZodOptional<z.ZodDefault<z.ZodBoolean>>;
90
+ dedupe_window_ms: z.ZodOptional<z.ZodDefault<z.ZodNumber>>;
91
+ max_same_directive_repeats: z.ZodOptional<z.ZodDefault<z.ZodNumber>>;
92
+ auto_continue: z.ZodOptional<z.ZodDefault<z.ZodBoolean>>;
93
+ auto_continue_delay_ms: z.ZodOptional<z.ZodDefault<z.ZodNumber>>;
94
+ max_auto_steps_per_session: z.ZodOptional<z.ZodDefault<z.ZodNumber>>;
95
+ }, z.core.$strip>>>;
96
+ truncation: z.ZodOptional<z.ZodDefault<z.ZodObject<{
97
+ enabled: z.ZodOptional<z.ZodDefault<z.ZodBoolean>>;
98
+ truncate_all_tool_outputs: z.ZodOptional<z.ZodDefault<z.ZodBoolean>>;
99
+ max_chars_default: z.ZodOptional<z.ZodDefault<z.ZodNumber>>;
100
+ max_chars_webfetch: z.ZodOptional<z.ZodDefault<z.ZodNumber>>;
101
+ max_chars_diff: z.ZodOptional<z.ZodDefault<z.ZodNumber>>;
102
+ persist_truncated_outputs: z.ZodOptional<z.ZodDefault<z.ZodBoolean>>;
103
+ }, z.core.$strip>>>;
104
+ context_compaction: z.ZodOptional<z.ZodDefault<z.ZodObject<{
105
+ enabled: z.ZodOptional<z.ZodDefault<z.ZodBoolean>>;
106
+ snapshot_after_stage_count: z.ZodOptional<z.ZodDefault<z.ZodNumber>>;
107
+ snapshot_max_lines: z.ZodOptional<z.ZodDefault<z.ZodNumber>>;
108
+ baton_summary_max_lines: z.ZodOptional<z.ZodDefault<z.ZodNumber>>;
109
+ inject_max_chars: z.ZodOptional<z.ZodDefault<z.ZodNumber>>;
110
+ }, z.core.$strip>>>;
111
+ artifacts: z.ZodOptional<z.ZodDefault<z.ZodObject<{
112
+ root_dir: z.ZodOptional<z.ZodDefault<z.ZodString>>;
113
+ runs_dir: z.ZodOptional<z.ZodDefault<z.ZodString>>;
114
+ spec_path: z.ZodOptional<z.ZodDefault<z.ZodString>>;
115
+ write_full_baton_md: z.ZodOptional<z.ZodDefault<z.ZodBoolean>>;
116
+ write_baton_summary_md: z.ZodOptional<z.ZodDefault<z.ZodBoolean>>;
117
+ write_baton_output_json: z.ZodOptional<z.ZodDefault<z.ZodBoolean>>;
118
+ baton_filename: z.ZodOptional<z.ZodDefault<z.ZodString>>;
119
+ baton_summary_filename: z.ZodOptional<z.ZodDefault<z.ZodString>>;
120
+ baton_json_filename: z.ZodOptional<z.ZodDefault<z.ZodString>>;
121
+ }, z.core.$strip>>>;
122
+ agents: z.ZodOptional<z.ZodDefault<z.ZodObject<{
123
+ orchestrator_name: z.ZodOptional<z.ZodDefault<z.ZodString>>;
124
+ stage_agent_names: z.ZodOptional<z.ZodDefault<z.ZodObject<{
125
+ frame: z.ZodOptional<z.ZodDefault<z.ZodString>>;
126
+ plan: z.ZodOptional<z.ZodDefault<z.ZodString>>;
127
+ spec: z.ZodOptional<z.ZodDefault<z.ZodString>>;
128
+ implement: z.ZodOptional<z.ZodDefault<z.ZodString>>;
129
+ review: z.ZodOptional<z.ZodDefault<z.ZodString>>;
130
+ verify: z.ZodOptional<z.ZodDefault<z.ZodString>>;
131
+ close: z.ZodOptional<z.ZodDefault<z.ZodString>>;
132
+ }, z.core.$strip>>>;
133
+ librarian_name: z.ZodOptional<z.ZodDefault<z.ZodString>>;
134
+ explore_name: z.ZodOptional<z.ZodDefault<z.ZodString>>;
135
+ agent_variant_overrides: z.ZodOptional<z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodObject<{
136
+ variant: z.ZodOptional<z.ZodString>;
137
+ model: z.ZodOptional<z.ZodString>;
138
+ }, z.core.$strip>>>>;
139
+ }, z.core.$strip>>>;
140
+ permissions: z.ZodOptional<z.ZodDefault<z.ZodObject<{
141
+ enforce_task_tool_restrictions: z.ZodOptional<z.ZodDefault<z.ZodBoolean>>;
142
+ deny_delegate_task_in_subagents: z.ZodOptional<z.ZodDefault<z.ZodBoolean>>;
143
+ }, z.core.$strip>>>;
144
+ git: z.ZodOptional<z.ZodDefault<z.ZodObject<{
145
+ enabled: z.ZodOptional<z.ZodDefault<z.ZodBoolean>>;
146
+ allow_dirty_start: z.ZodOptional<z.ZodDefault<z.ZodBoolean>>;
147
+ auto_branch: z.ZodOptional<z.ZodDefault<z.ZodBoolean>>;
148
+ branch_prefix: z.ZodOptional<z.ZodDefault<z.ZodString>>;
149
+ auto_commit: z.ZodOptional<z.ZodDefault<z.ZodBoolean>>;
150
+ commit_message_template: z.ZodOptional<z.ZodDefault<z.ZodString>>;
151
+ persist_diff_artifacts: z.ZodOptional<z.ZodDefault<z.ZodBoolean>>;
152
+ }, z.core.$strip>>>;
153
+ ui: z.ZodOptional<z.ZodDefault<z.ZodObject<{
154
+ toasts: z.ZodOptional<z.ZodDefault<z.ZodObject<{
155
+ enabled: z.ZodOptional<z.ZodDefault<z.ZodBoolean>>;
156
+ throttle_ms: z.ZodOptional<z.ZodDefault<z.ZodNumber>>;
157
+ show_run_started: z.ZodOptional<z.ZodDefault<z.ZodBoolean>>;
158
+ show_stage_started: z.ZodOptional<z.ZodDefault<z.ZodBoolean>>;
159
+ show_stage_completed: z.ZodOptional<z.ZodDefault<z.ZodBoolean>>;
160
+ show_stage_failed: z.ZodOptional<z.ZodDefault<z.ZodBoolean>>;
161
+ show_run_completed: z.ZodOptional<z.ZodDefault<z.ZodBoolean>>;
162
+ show_auto_continue: z.ZodOptional<z.ZodDefault<z.ZodBoolean>>;
163
+ }, z.core.$strip>>>;
164
+ continue_prompt: z.ZodOptional<z.ZodDefault<z.ZodObject<{
165
+ enabled: z.ZodOptional<z.ZodDefault<z.ZodBoolean>>;
166
+ mode: z.ZodOptional<z.ZodDefault<z.ZodEnum<{
167
+ toast_button: "toast_button";
168
+ popup: "popup";
169
+ chat_only: "chat_only";
170
+ }>>>;
171
+ idle_prompt_ms: z.ZodOptional<z.ZodDefault<z.ZodNumber>>;
172
+ }, z.core.$strip>>>;
173
+ }, z.core.$strip>>>;
174
+ }, z.core.$strip>>;
175
+ export type AstrocodeConfig = z.infer<typeof AstrocodeConfigSchema>;
176
+ export {};
@@ -0,0 +1,198 @@
1
+ import { z } from "zod";
2
+ export const PermissionValueSchema = z.enum(["ask", "allow", "deny"]);
3
+ const StageKeySchema = z.enum([
4
+ "frame",
5
+ "plan",
6
+ "spec",
7
+ "implement",
8
+ "review",
9
+ "verify",
10
+ "close",
11
+ ]);
12
+ const InjectionModeSchema = z.enum(["visible", "silent"]);
13
+ const TruncationPolicySchema = z
14
+ .object({
15
+ enabled: z.boolean().default(true),
16
+ truncate_all_tool_outputs: z.boolean().default(false),
17
+ max_chars_default: z.number().int().positive().default(200_000),
18
+ max_chars_webfetch: z.number().int().positive().default(40_000),
19
+ max_chars_diff: z.number().int().positive().default(120_000),
20
+ persist_truncated_outputs: z.boolean().default(true),
21
+ })
22
+ .partial()
23
+ .default({});
24
+ const ContextCompactionSchema = z
25
+ .object({
26
+ enabled: z.boolean().default(true),
27
+ snapshot_after_stage_count: z.number().int().positive().default(2),
28
+ snapshot_max_lines: z.number().int().positive().default(60),
29
+ baton_summary_max_lines: z.number().int().positive().default(25),
30
+ inject_max_chars: z.number().int().positive().default(18_000),
31
+ })
32
+ .partial()
33
+ .default({});
34
+ const ContinuationSchema = z
35
+ .object({
36
+ enabled: z.boolean().default(true),
37
+ injection_mode: InjectionModeSchema.default("visible"),
38
+ inject_on_session_idle: z.boolean().default(true),
39
+ session_idle_ms: z.number().int().positive().default(12_000),
40
+ inject_on_tool_done_if_run_active: z.boolean().default(true),
41
+ inject_on_message_done_if_run_active: z.boolean().default(true),
42
+ dedupe_window_ms: z.number().int().positive().default(20_000),
43
+ max_same_directive_repeats: z.number().int().positive().default(1),
44
+ auto_continue: z.boolean().default(false),
45
+ auto_continue_delay_ms: z.number().int().positive().default(1500),
46
+ max_auto_steps_per_session: z.number().int().positive().default(50),
47
+ })
48
+ .partial()
49
+ .default({});
50
+ const ToastsSchema = z
51
+ .object({
52
+ enabled: z.boolean().default(true),
53
+ throttle_ms: z.number().int().positive().default(1500),
54
+ show_run_started: z.boolean().default(true),
55
+ show_stage_started: z.boolean().default(true),
56
+ show_stage_completed: z.boolean().default(true),
57
+ show_stage_failed: z.boolean().default(true),
58
+ show_run_completed: z.boolean().default(true),
59
+ show_auto_continue: z.boolean().default(true),
60
+ })
61
+ .partial()
62
+ .default({});
63
+ const DbSchema = z
64
+ .object({
65
+ path: z.string().default(".astro/astro.db"),
66
+ busy_timeout_ms: z.number().int().positive().default(5000),
67
+ pragmas: z
68
+ .object({
69
+ journal_mode: z.enum(["WAL", "DELETE"]).default("WAL"),
70
+ synchronous: z.enum(["NORMAL", "FULL", "OFF"]).default("NORMAL"),
71
+ foreign_keys: z.boolean().default(true),
72
+ temp_store: z.enum(["DEFAULT", "MEMORY", "FILE"]).default("MEMORY"),
73
+ })
74
+ // Why: Zod's .default({}) requires the *type* to accept an empty object.
75
+ // We still want runtime defaults for each key, so we make keys optional.
76
+ .partial()
77
+ .default({}),
78
+ schema_version_required: z.number().int().positive().default(2),
79
+ allow_auto_migrate: z.boolean().default(true),
80
+ fail_on_downgrade: z.boolean().default(true),
81
+ })
82
+ // Why: allow "db: {}" (or missing db) while still applying defaults.
83
+ .partial()
84
+ .default({});
85
+ const WorkflowSchema = z.object({
86
+ pipeline: z.array(StageKeySchema).default([
87
+ "frame",
88
+ "plan",
89
+ "spec",
90
+ "implement",
91
+ "review",
92
+ "verify",
93
+ "close",
94
+ ]),
95
+ default_mode: z.enum(["step", "loop"]).default("step"),
96
+ default_max_steps: z.number().int().positive().default(1),
97
+ loop_max_steps_hard_cap: z.number().int().positive().default(200),
98
+ plan_max_tasks: z.number().int().positive().default(7),
99
+ plan_max_lines: z.number().int().positive().default(80),
100
+ forbid_prompt_narration: z.boolean().default(true),
101
+ single_active_run_per_repo: z.boolean().default(true),
102
+ lock_timeout_ms: z.number().int().positive().default(4000),
103
+ role_first_subagents: z.boolean().default(true),
104
+ evidence_required: z
105
+ .object({
106
+ verify: z.boolean().default(true),
107
+ implement: z.boolean().default(false),
108
+ })
109
+ // NOTE: We want callers to be able to omit the whole object ("{}")
110
+ // while still receiving per-field defaults at parse time.
111
+ .partial()
112
+ .default({}),
113
+ }).partial().default({});
114
+ const ArtifactsSchema = z.object({
115
+ root_dir: z.string().default(".astro"),
116
+ runs_dir: z.string().default(".astro/runs"),
117
+ spec_path: z.string().default(".astro/spec.md"),
118
+ write_full_baton_md: z.boolean().default(true),
119
+ write_baton_summary_md: z.boolean().default(true),
120
+ write_baton_output_json: z.boolean().default(true),
121
+ baton_filename: z.string().default("baton.md"),
122
+ baton_summary_filename: z.string().default("baton.summary.md"),
123
+ baton_json_filename: z.string().default("baton.json"),
124
+ }).partial().default({});
125
+ const AgentsSchema = z.object({
126
+ // Display name for the *primary* agent tab.
127
+ orchestrator_name: z.string().default("Astro"),
128
+ // Display names for the stage sub-agents.
129
+ stage_agent_names: z
130
+ .object({
131
+ frame: z.string().default("Astro — Frame"),
132
+ plan: z.string().default("Astro — Plan"),
133
+ spec: z.string().default("Astro — Spec"),
134
+ implement: z.string().default("Astro — Implement"),
135
+ review: z.string().default("Astro — Review"),
136
+ verify: z.string().default("Astro — Verify"),
137
+ close: z.string().default("Astro — Close"),
138
+ })
139
+ .partial()
140
+ .default({}),
141
+ librarian_name: z.string().default("Astro — Librarian"),
142
+ explore_name: z.string().default("Astro — Explore"),
143
+ agent_variant_overrides: z
144
+ .record(z.string(), z.object({
145
+ variant: z.string().optional(),
146
+ model: z.string().optional(),
147
+ }))
148
+ .default({}),
149
+ }).partial().default({});
150
+ const PermissionsSchema = z.object({
151
+ enforce_task_tool_restrictions: z.boolean().default(true),
152
+ deny_delegate_task_in_subagents: z.boolean().default(true),
153
+ }).partial().default({});
154
+ const GitSchema = z.object({
155
+ enabled: z.boolean().default(true),
156
+ allow_dirty_start: z.boolean().default(true),
157
+ auto_branch: z.boolean().default(true),
158
+ branch_prefix: z.string().default("astro/"),
159
+ auto_commit: z.boolean().default(false),
160
+ commit_message_template: z.string().default("astro: {{story_key}} {{title}}"),
161
+ persist_diff_artifacts: z.boolean().default(true),
162
+ }).partial().default({});
163
+ const UiSchema = z
164
+ .object({
165
+ toasts: ToastsSchema,
166
+ continue_prompt: z
167
+ .object({
168
+ enabled: z.boolean().default(true),
169
+ mode: z.enum(["toast_button", "popup", "chat_only"]).default("toast_button"),
170
+ idle_prompt_ms: z.number().int().positive().default(20_000),
171
+ })
172
+ .partial()
173
+ .default({}),
174
+ })
175
+ .partial()
176
+ .default({});
177
+ export const AstrocodeConfigSchema = z.object({
178
+ disabled_hooks: z.array(z.string()).default([]),
179
+ disabled_agents: z.array(z.string()).default([]),
180
+ disabled_commands: z.array(z.string()).default([]),
181
+ determinism: z
182
+ .object({
183
+ mode: z.enum(["on", "off"]).default("on"),
184
+ strict_stage_order: z.boolean().default(true),
185
+ })
186
+ .partial()
187
+ .default({}),
188
+ db: DbSchema,
189
+ workflow: WorkflowSchema,
190
+ continuation: ContinuationSchema,
191
+ truncation: TruncationPolicySchema,
192
+ context_compaction: ContextCompactionSchema,
193
+ artifacts: ArtifactsSchema,
194
+ agents: AgentsSchema,
195
+ permissions: PermissionsSchema,
196
+ git: GitSchema,
197
+ ui: UiSchema,
198
+ }).partial().default({});
@@ -0,0 +1,26 @@
1
+ import type { AstrocodeConfig } from "../config/schema";
2
+ import type { SqliteDb } from "../state/db";
3
+ type ToolExecuteAfterInput = {
4
+ tool: string;
5
+ sessionID?: string;
6
+ };
7
+ type ChatMessageInput = {
8
+ sessionID: string;
9
+ agent: string;
10
+ };
11
+ type EventInput = {
12
+ event: {
13
+ type: string;
14
+ properties: any;
15
+ };
16
+ };
17
+ export declare function createContinuationEnforcer(opts: {
18
+ ctx: any;
19
+ config: AstrocodeConfig;
20
+ db: SqliteDb;
21
+ }): {
22
+ onToolAfter(input: ToolExecuteAfterInput): Promise<void>;
23
+ onChatMessage(_input: ChatMessageInput): Promise<void>;
24
+ onEvent(input: EventInput): Promise<void>;
25
+ };
26
+ export {};
@@ -0,0 +1,166 @@
1
+ import { buildContextSnapshot } from "../workflow/context";
2
+ import { decideNextAction, getActiveRun } from "../workflow/state-machine";
3
+ import { buildContinueDirective } from "../workflow/directives";
4
+ import { injectChatPrompt } from "../ui/inject";
5
+ import { nowISO } from "../shared/time";
6
+ import { createToastManager } from "../ui/toasts";
7
+ function msFromIso(iso) {
8
+ const t = Date.parse(iso);
9
+ return Number.isFinite(t) ? t : 0;
10
+ }
11
+ export function createContinuationEnforcer(opts) {
12
+ const { ctx, config, db } = opts;
13
+ const toasts = createToastManager({ ctx, throttleMs: config.ui.toasts.throttle_ms });
14
+ const sessions = new Map();
15
+ function getState(sessionId) {
16
+ const cur = sessions.get(sessionId);
17
+ if (cur)
18
+ return cur;
19
+ const state = { lastHash: null, lastAtMs: 0, repeats: 0, autoSteps: 0, idleTimer: null };
20
+ sessions.set(sessionId, state);
21
+ return state;
22
+ }
23
+ function clearIdleTimer(sessionId) {
24
+ const s = getState(sessionId);
25
+ if (s.idleTimer) {
26
+ clearTimeout(s.idleTimer);
27
+ s.idleTimer = null;
28
+ }
29
+ }
30
+ function scheduleIdleInjection(sessionId) {
31
+ clearIdleTimer(sessionId);
32
+ if (!config.continuation.enabled)
33
+ return;
34
+ if (!config.continuation.inject_on_session_idle)
35
+ return;
36
+ const delay = config.continuation.session_idle_ms;
37
+ const s = getState(sessionId);
38
+ s.idleTimer = setTimeout(() => {
39
+ // Fire and forget
40
+ void maybeInjectContinue(sessionId, "idle_timer");
41
+ }, delay);
42
+ }
43
+ function shouldDedupe(sessionId, directive) {
44
+ const s = getState(sessionId);
45
+ const now = Date.now();
46
+ // Memory window
47
+ if (s.lastHash === directive.hash && now - s.lastAtMs < config.continuation.dedupe_window_ms) {
48
+ if (s.repeats >= config.continuation.max_same_directive_repeats)
49
+ return true;
50
+ }
51
+ // DB window (durable)
52
+ const cutoff = new Date(now - config.continuation.dedupe_window_ms).toISOString();
53
+ const row = db
54
+ .prepare("SELECT COUNT(*) as c FROM continuations WHERE session_id=? AND directive_hash=? AND created_at > ?")
55
+ .get(sessionId, directive.hash, cutoff);
56
+ if ((row?.c ?? 0) >= config.continuation.max_same_directive_repeats)
57
+ return true;
58
+ return false;
59
+ }
60
+ async function recordContinuation(sessionId, runId, directive, reason) {
61
+ db.prepare("INSERT INTO continuations (session_id, run_id, directive_hash, kind, reason, created_at) VALUES (?, ?, ?, ?, ?, ?)").run(sessionId, runId, directive.hash, directive.kind, reason, nowISO());
62
+ const s = getState(sessionId);
63
+ const now = Date.now();
64
+ if (s.lastHash === directive.hash && now - s.lastAtMs < config.continuation.dedupe_window_ms) {
65
+ s.repeats += 1;
66
+ }
67
+ else {
68
+ s.lastHash = directive.hash;
69
+ s.repeats = 1;
70
+ }
71
+ s.lastAtMs = now;
72
+ }
73
+ function formatNextAction(next) {
74
+ switch (next.kind) {
75
+ case "idle":
76
+ return "No approved stories. Queue/approve a story.";
77
+ case "start_run":
78
+ return `Start run for story ${next.story_key}.`;
79
+ case "delegate_stage":
80
+ return `Delegate stage ${next.stage_key}.`;
81
+ case "await_stage_completion":
82
+ return `Await stage ${next.stage_key} completion. If you have stage output, call astro_stage_complete.`;
83
+ case "complete_run":
84
+ return `Complete run ${next.run_id}.`;
85
+ case "failed":
86
+ return `Run failed at stage ${next.stage_key}: ${next.error_text}`;
87
+ default:
88
+ return "Continue.";
89
+ }
90
+ }
91
+ async function maybeInjectContinue(sessionId, reason) {
92
+ if (!config.continuation.enabled)
93
+ return;
94
+ // Require active run
95
+ const active = getActiveRun(db);
96
+ if (!active)
97
+ return;
98
+ const next = decideNextAction(db, config);
99
+ // If failed, don't auto-inject "continue" — surface via toast and stop.
100
+ if (next.kind === "failed") {
101
+ if (config.ui.toasts.enabled && config.ui.toasts.show_stage_failed) {
102
+ await toasts.show({ title: "Astrocode", message: `Run failed at ${next.stage_key}`, variant: "error" });
103
+ }
104
+ return;
105
+ }
106
+ const nextActionStr = formatNextAction(next);
107
+ const context = buildContextSnapshot({ db, config, run_id: active.run_id, next_action: nextActionStr });
108
+ const directive = buildContinueDirective({
109
+ config,
110
+ run_id: active.run_id,
111
+ stage_key: active.current_stage_key,
112
+ next_action: nextActionStr,
113
+ context_snapshot_md: context,
114
+ });
115
+ if (shouldDedupe(sessionId, directive))
116
+ return;
117
+ await recordContinuation(sessionId, active.run_id, directive, reason);
118
+ // Injection mode
119
+ if (config.continuation.injection_mode === "visible") {
120
+ await injectChatPrompt({ ctx, sessionId, text: directive.body });
121
+ }
122
+ else {
123
+ // Silent mode is TODO: requires experimental.chat.messages.transform.
124
+ // For v2-alpha, we fall back to visible injection but mark it.
125
+ await injectChatPrompt({ ctx, sessionId, text: directive.body + "\n\n(Injected in silent mode fallback)" });
126
+ }
127
+ if (config.ui.toasts.enabled && config.ui.toasts.show_auto_continue) {
128
+ await toasts.show({ title: "Astrocode", message: "Continue directive injected", variant: "info" });
129
+ }
130
+ }
131
+ // Public hook handlers
132
+ return {
133
+ async onToolAfter(input) {
134
+ const sessionId = input.sessionID ?? ctx.sessionID;
135
+ if (!sessionId)
136
+ return;
137
+ if (!config.continuation.inject_on_tool_done_if_run_active)
138
+ return;
139
+ scheduleIdleInjection(sessionId);
140
+ },
141
+ async onChatMessage(_input) {
142
+ if (!config.continuation.inject_on_message_done_if_run_active)
143
+ return;
144
+ scheduleIdleInjection(_input.sessionID);
145
+ },
146
+ async onEvent(input) {
147
+ const type = input.event.type;
148
+ const sessionId = input.event.properties?.sessionID;
149
+ if (!sessionId)
150
+ return;
151
+ if (type === "session.idle") {
152
+ if (!config.continuation.inject_on_session_idle)
153
+ return;
154
+ await maybeInjectContinue(sessionId, "session.idle");
155
+ }
156
+ if (type === "session.created") {
157
+ // When a session is created and there is an active run, nudge.
158
+ scheduleIdleInjection(sessionId);
159
+ }
160
+ if (type === "session.deleted") {
161
+ clearIdleTimer(sessionId);
162
+ sessions.delete(sessionId);
163
+ }
164
+ },
165
+ };
166
+ }
@@ -0,0 +1,17 @@
1
+ import type { AstrocodeConfig } from "../config/schema";
2
+ import type { SqliteDb } from "../state/db";
3
+ type ToolExecuteAfterInput = {
4
+ tool: string;
5
+ sessionID?: string;
6
+ };
7
+ type ToolExecuteAfterOutput = {
8
+ title?: string;
9
+ output?: string;
10
+ metadata?: Record<string, any>;
11
+ };
12
+ export declare function createToolOutputTruncatorHook(opts: {
13
+ ctx: any;
14
+ config: AstrocodeConfig;
15
+ db: SqliteDb;
16
+ }): (input: ToolExecuteAfterInput, output: ToolExecuteAfterOutput) => Promise<void>;
17
+ export {};
@@ -0,0 +1,56 @@
1
+ import path from "node:path";
2
+ import { getAstroPaths, ensureDir, toPosix } from "../shared/paths";
3
+ import { nowISO } from "../shared/time";
4
+ import { putArtifact } from "../workflow/artifacts";
5
+ import { sha256Hex } from "../shared/hash";
6
+ import { clampChars } from "../shared/text";
7
+ import { getActiveRun } from "../workflow/state-machine";
8
+ function pickMaxChars(toolName, cfg) {
9
+ if (toolName.includes("webfetch") || toolName.includes("web.run"))
10
+ return cfg.truncation.max_chars_webfetch;
11
+ if (toolName.includes("diff"))
12
+ return cfg.truncation.max_chars_diff;
13
+ return cfg.truncation.max_chars_default;
14
+ }
15
+ export function createToolOutputTruncatorHook(opts) {
16
+ const { ctx, config, db } = opts;
17
+ return async function toolExecuteAfter(input, output) {
18
+ if (!config.truncation.enabled)
19
+ return;
20
+ const toolName = input.tool;
21
+ const text = output.output ?? "";
22
+ const maxChars = pickMaxChars(toolName, config);
23
+ if (!text || text.length <= maxChars)
24
+ return;
25
+ const repoRoot = ctx.directory;
26
+ const paths = getAstroPaths(repoRoot, config.db.path);
27
+ ensureDir(paths.toolOutputDir);
28
+ const active = getActiveRun(db);
29
+ const timestamp = nowISO().replace(/[:.]/g, "-");
30
+ const relBase = active && config.truncation.persist_truncated_outputs
31
+ ? toPosix(path.join(".astro", "runs", active.run_id, "_tool_output"))
32
+ : toPosix(path.join(".astro", "tool_output"));
33
+ const relPath = toPosix(path.join(relBase, toolName.replace(/[^a-zA-Z0-9_-]/g, "_"), `${timestamp}.txt`));
34
+ const { artifact_id } = putArtifact({
35
+ repoRoot,
36
+ db,
37
+ run_id: active?.run_id ?? null,
38
+ stage_key: active?.current_stage_key ?? null,
39
+ type: "tool_output",
40
+ rel_path: relPath,
41
+ content: text,
42
+ meta: { tool: toolName, session_id: input.sessionID ?? null },
43
+ });
44
+ const digest = sha256Hex(text);
45
+ const head = clampChars(text, Math.min(maxChars, 4000));
46
+ output.output =
47
+ head +
48
+ `\n\n…(truncated; sha256=${digest})\n` +
49
+ `Full output saved: ${relPath}\n` +
50
+ `Artifact ID: ${artifact_id}`;
51
+ output.metadata = output.metadata ?? {};
52
+ output.metadata.truncated = true;
53
+ output.metadata.artifact_id = artifact_id;
54
+ output.metadata.full_output_path = relPath;
55
+ };
56
+ }
@@ -0,0 +1,3 @@
1
+ import type { Plugin } from "@opencode-ai/plugin";
2
+ declare const Astrocode: Plugin;
3
+ export default Astrocode;