fifony 0.1.40 → 0.1.42

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 (33) hide show
  1. package/README.md +12 -11
  2. package/app/dist/assets/{CommandPalette-dMSFpGLm.js → CommandPalette-DNR5umI1.js} +1 -1
  3. package/app/dist/assets/{KeyboardShortcutsHelp-CH5aYlDe.js → KeyboardShortcutsHelp-Dpl19F20.js} +1 -1
  4. package/app/dist/assets/OnboardingWizard-CijMhJDW.js +1 -0
  5. package/app/dist/assets/analytics.lazy-Dq90a756.js +1 -0
  6. package/app/dist/assets/index-Dy_fM427.js +54 -0
  7. package/app/dist/assets/{index-BatA8x-K.css → index-Q9jBP0Pz.css} +1 -1
  8. package/app/dist/index.html +2 -2
  9. package/app/dist/service-worker.js +53 -2
  10. package/dist/agent/run-local.js +25 -9
  11. package/dist/{agent-KDPOZCI5.js → agent-NNGZEKZH.js} +8 -7
  12. package/dist/{chunk-EPY5TTQK.js → chunk-2CVTK5F2.js} +6 -6
  13. package/dist/{chunk-UXXUTDGV.js → chunk-H5N7O5NP.js} +33 -17
  14. package/dist/chunk-I2UHVKHS.js +104 -0
  15. package/dist/{chunk-LYAI5RPK.js → chunk-JTKUWIQD.js} +3634 -3453
  16. package/dist/{chunk-O3FGX4J6.js → chunk-NB44PCD2.js} +115 -24
  17. package/dist/{chunk-3QL4QAQ5.js → chunk-RBDBGU2C.js} +14 -14
  18. package/dist/cli.js +7 -6
  19. package/dist/issue-runner-CMZPSVC7.js +16 -0
  20. package/dist/{issue-state-machine-KOZE5JWX.js → issue-state-machine-GPQNZYUZ.js} +5 -5
  21. package/dist/{issues-7HQC7OIN.js → issues-MZLRSXD6.js} +8 -7
  22. package/dist/mcp/server.js +1 -1
  23. package/dist/{queue-workers-ZEZHDX7M.js → queue-workers-XZ6DGH4W.js} +3 -3
  24. package/dist/{scheduler-4R4ZAF25.js → scheduler-NVE6L3P7.js} +8 -7
  25. package/dist/settings-NGY33WQE.js +74 -0
  26. package/dist/{store-FNUWCFOX.js → store-4HCGBN4L.js} +8 -7
  27. package/dist/web-push-CRVDJKWR.js +28 -0
  28. package/dist/{workspace-AOHHNWL5.js → workspace-D3F3XGSI.js} +8 -4
  29. package/package.json +6 -2
  30. package/app/dist/assets/OnboardingWizard-CdJHsRny.js +0 -1
  31. package/app/dist/assets/analytics.lazy-CKu4136R.js +0 -1
  32. package/app/dist/assets/index-C13WwYFD.js +0 -54
  33. package/dist/issue-runner-YZM6WQMY.js +0 -15
@@ -20,12 +20,12 @@
20
20
  <link rel="icon" href="/assets/icon-32.png" sizes="32x32" type="image/png" />
21
21
  <link rel="icon" href="/assets/icon-16.png" sizes="16x16" type="image/png" />
22
22
  <link rel="apple-touch-icon" href="/assets/apple-touch-icon.png" />
23
- <script type="module" crossorigin src="/assets/assets/index-C13WwYFD.js"></script>
23
+ <script type="module" crossorigin src="/assets/assets/index-Dy_fM427.js"></script>
24
24
  <link rel="modulepreload" crossorigin href="/assets/assets/rolldown-runtime-Dw2cE7zH.js">
25
25
  <link rel="modulepreload" crossorigin href="/assets/assets/api-ChEctgc5.js">
26
26
  <link rel="modulepreload" crossorigin href="/assets/assets/vendor-DkWeBvNl.js">
27
27
  <link rel="modulepreload" crossorigin href="/assets/assets/createLucideIcon-R47sXufx.js">
28
- <link rel="stylesheet" crossorigin href="/assets/assets/index-BatA8x-K.css">
28
+ <link rel="stylesheet" crossorigin href="/assets/assets/index-Q9jBP0Pz.css">
29
29
  </head>
30
30
  <body>
31
31
  <div id="root"></div>
@@ -1,7 +1,21 @@
1
- const CACHE_VERSION = "1774372039411";
1
+ const CACHE_VERSION = "1774421145935";
2
2
  const CORE_CACHE = `fifony-core-${CACHE_VERSION}`;
3
3
  const ASSET_CACHE = `fifony-assets-${CACHE_VERSION}`;
4
- const APP_SHELL_ROUTES = ["/kanban", "/issues", "/agents", "/settings", "/onboarding"];
4
+ const APP_SHELL_ROUTES = [
5
+ "/onboarding",
6
+ "/kanban",
7
+ "/issues",
8
+ "/analytics",
9
+ "/agents",
10
+ "/settings",
11
+ "/settings/project",
12
+ "/settings/general",
13
+ "/settings/agents",
14
+ "/settings/notifications",
15
+ "/settings/workflow",
16
+ "/settings/hotkeys",
17
+ "/settings/providers",
18
+ ];
5
19
  const APP_SHELL_FILES = ["/offline.html", "/manifest.webmanifest", "/favicon.png", "/icon-192.png", "/icon-512.png"];
6
20
  const API_PREFIXES = ["/api/", "/docs", "/ws"];
7
21
 
@@ -134,6 +148,43 @@ self.addEventListener("fetch", (event) => {
134
148
  })());
135
149
  });
136
150
 
151
+ // ── Web Push notification handler ─────────────────────────────────────────
152
+
153
+ self.addEventListener("push", (event) => {
154
+ if (!event.data) return;
155
+ try {
156
+ const data = event.data.json();
157
+ const title = data.title || "fifony";
158
+ const options = {
159
+ body: data.body || "",
160
+ icon: "/icon-192.png",
161
+ badge: "/favicon.png",
162
+ tag: data.tag || "fifony-push",
163
+ data: { url: data.url || "/kanban" },
164
+ };
165
+ event.waitUntil(self.registration.showNotification(title, options));
166
+ } catch {
167
+ // Malformed push payload — ignore
168
+ }
169
+ });
170
+
171
+ self.addEventListener("notificationclick", (event) => {
172
+ event.notification.close();
173
+ const url = event.notification.data?.url || "/kanban";
174
+ event.waitUntil(
175
+ self.clients.matchAll({ type: "window" }).then((clients) => {
176
+ // Focus existing tab if open
177
+ for (const client of clients) {
178
+ if (client.url.includes(url) && "focus" in client) {
179
+ return client.focus();
180
+ }
181
+ }
182
+ // Otherwise open new tab
183
+ return self.clients.openWindow(url);
184
+ }),
185
+ );
186
+ });
187
+
137
188
  // Broadcast offline status to all window clients
138
189
  async function notifyClientsOffline() {
139
190
  try {
@@ -16,6 +16,7 @@ import {
16
16
  loadPersistedState,
17
17
  loadRuntimeSettings,
18
18
  persistDetectedProvidersSetting,
19
+ persistSetting,
19
20
  persistState,
20
21
  persistStateFull,
21
22
  recoverPlanningSession,
@@ -23,17 +24,20 @@ import {
23
24
  startApiServer,
24
25
  syncRuntimeConfigSettings,
25
26
  validateConfig
26
- } from "../chunk-LYAI5RPK.js";
27
+ } from "../chunk-JTKUWIQD.js";
27
28
  import {
28
29
  cleanTerminalWorkspaces,
29
30
  initQueueWorkers,
30
31
  recoverOrphans,
31
32
  recoverState,
32
33
  stopQueueWorkers
33
- } from "../chunk-3QL4QAQ5.js";
34
+ } from "../chunk-RBDBGU2C.js";
35
+ import {
36
+ initWebPush
37
+ } from "../chunk-I2UHVKHS.js";
34
38
  import {
35
39
  computeMetrics
36
- } from "../chunk-UXXUTDGV.js";
40
+ } from "../chunk-H5N7O5NP.js";
37
41
  import {
38
42
  detectAvailableProviders,
39
43
  detectDefaultBranch,
@@ -41,23 +45,23 @@ import {
41
45
  getProviderDefaultCommand,
42
46
  resolveDefaultProvider,
43
47
  setSkipSource
44
- } from "../chunk-O3FGX4J6.js";
45
- import {
46
- initLogger,
47
- logger
48
- } from "../chunk-DVU3CXWA.js";
48
+ } from "../chunk-NB44PCD2.js";
49
49
  import {
50
50
  debugBoot,
51
51
  fail,
52
52
  now,
53
53
  parseIntArg
54
- } from "../chunk-EPY5TTQK.js";
54
+ } from "../chunk-2CVTK5F2.js";
55
55
  import {
56
56
  CLI_ARGS,
57
57
  PACKAGE_ROOT,
58
58
  STATE_ROOT,
59
59
  TARGET_ROOT
60
60
  } from "../chunk-37N5OFHM.js";
61
+ import {
62
+ initLogger,
63
+ logger
64
+ } from "../chunk-DVU3CXWA.js";
61
65
 
62
66
  // src/boot.ts
63
67
  import { mkdirSync, readFileSync } from "fs";
@@ -316,6 +320,18 @@ async function main() {
316
320
  logger.warn({ err: error }, "[Boot] Queue workers failed to initialize \u2014 continuing without queue-based dispatch");
317
321
  }
318
322
  installGracefulShutdown(state);
323
+ try {
324
+ const settings = await loadRuntimeSettings();
325
+ await initWebPush(
326
+ async (id) => settings.find((s) => s.id === id)?.value,
327
+ async (id, value, scope) => {
328
+ const normalizedScope = scope === "runtime" || scope === "providers" || scope === "ui" || scope === "system" ? scope : "system";
329
+ await persistSetting(id, value, { scope: normalizedScope, source: "system" });
330
+ }
331
+ );
332
+ } catch (err) {
333
+ logger.warn({ err: String(err) }, "[Boot] Web push init failed \u2014 push notifications disabled");
334
+ }
319
335
  logger.info("[Boot] Runtime ready");
320
336
  hydrate(state.issues);
321
337
  logger.info(`Loaded issues: ${state.issues.length}`);
@@ -15,9 +15,10 @@ import {
15
15
  runIssueOnce,
16
16
  runPlanningJob,
17
17
  tryParseJsonOutput
18
- } from "./chunk-LYAI5RPK.js";
19
- import "./chunk-3QL4QAQ5.js";
20
- import "./chunk-UXXUTDGV.js";
18
+ } from "./chunk-JTKUWIQD.js";
19
+ import "./chunk-RBDBGU2C.js";
20
+ import "./chunk-I2UHVKHS.js";
21
+ import "./chunk-H5N7O5NP.js";
21
22
  import {
22
23
  buildPrompt,
23
24
  buildProviderBasePrompt,
@@ -34,10 +35,10 @@ import {
34
35
  runCommandWithTimeout,
35
36
  runHook,
36
37
  shouldSkipMergePath
37
- } from "./chunk-O3FGX4J6.js";
38
- import "./chunk-DVU3CXWA.js";
39
- import "./chunk-EPY5TTQK.js";
38
+ } from "./chunk-NB44PCD2.js";
39
+ import "./chunk-2CVTK5F2.js";
40
40
  import "./chunk-37N5OFHM.js";
41
+ import "./chunk-DVU3CXWA.js";
41
42
  export {
42
43
  addTokenUsage,
43
44
  buildPrompt,
@@ -71,4 +72,4 @@ export {
71
72
  shouldSkipMergePath,
72
73
  tryParseJsonOutput
73
74
  };
74
- //# sourceMappingURL=agent-KDPOZCI5.js.map
75
+ //# sourceMappingURL=agent-NNGZEKZH.js.map
@@ -186,11 +186,11 @@ var PROMPT_TEMPLATES = {
186
186
  // src/agents/prompts/agent-provider-base.stub.md
187
187
  "agent-turn": 'Continue working on {{issueIdentifier}}.\nTurn {{turnIndex}} of {{maxTurns}}.\n\nBase objective:\n{{basePrompt}}\n\nContinuation guidance:\n{{continuation}}\n\nPrevious command output tail:\n```text\n{{outputTail}}\n```\n\nBefore exiting successfully, emit one of the following control markers:\n- `FIFONY_STATUS=continue` if more turns are required.\n- `FIFONY_STATUS=done` if the issue is complete.\n- `FIFONY_STATUS=blocked` if manual intervention is required.\nYou may also write `fifony-result.json` with `{ "status": "...", "summary": "...", "nextPrompt": "..." }`.\n',
188
188
  // src/agents/prompts/agent-turn.stub.md
189
- "compile-execution-claude": '{{#if isPlanner}}\nRole: planner. Analyze the issue and prepare an execution plan.\n{{else}}\n{{#if isReviewer}}\nRole: reviewer. Inspect and review the implementation critically.\n{{else}}\nRole: executor. Implement the required changes.\n{{/if}}\n{{/if}}\n\n{{#if profileInstructions}}\n## Agent Profile\n{{profileInstructions}}\n{{/if}}\n\n{{#if capabilitiesManifest}}\n{{capabilitiesManifest}}\n{{/if}}\n\n{{#if skillContext}}\n{{skillContext}}\n{{/if}}\n\n{{planPrompt}}\n\n{{#if suggestedAgents.length}}\n## Agents (Claude-specific)\nYou have access to the Agent tool for spawning subagents. Use these agents for this task:\n{{#each suggestedAgents}}\n- Use the **{{this}}** agent for specialized work in its domain.\n{{/each}}\n\nLaunch agents for independent subtasks to maximize parallelism.\nUse the main thread for coordination and integration.\n{{/if}}\n\n{{#if suggestedSkills.length}}\n## Skills\nInvoke these skills during execution:\n{{#each suggestedSkills}}\n- Run **/{{this}}** for specialized quality checks and procedures.\n{{/each}}\n{{/if}}\n\n{{#if suggestedPaths.length}}\nTarget paths: {{suggestedPaths | join ", "}}\n{{/if}}\n\nWorkspace: {{workspacePath}}\n\nIssue: {{issueIdentifier}}\nTitle: {{title}}\nDescription: {{description}}\n\n## Structured Input\nThe file `fifony-execution-payload.json` in the workspace contains the canonical structured data for this task.\nUse it as the source of truth for constraints, success criteria, execution intent, and plan details.\nIf there is any conflict between this prompt and the structured fields in the payload, prioritize the payload.\n\n{{#if validationItems.length}}\n## Pre-completion enforcement\nBefore reporting done, verify:\n{{#each validationItems}}\n- {{value}}\n{{/each}}\n{{/if}}\n',
189
+ "compile-execution-claude": '{{#if isPlanner}}\nRole: planner. Analyze the issue and prepare an execution plan.\n{{else}}\n{{#if isReviewer}}\nRole: reviewer. Inspect and review the implementation critically.\n{{else}}\nRole: executor. Implement the required changes.\n{{/if}}\n{{/if}}\n\n{{#if profileInstructions}}\n## Agent Profile\n{{profileInstructions}}\n{{/if}}\n\n{{#if capabilitiesManifest}}\n{{capabilitiesManifest}}\n{{/if}}\n\n{{#if skillContext}}\n{{skillContext}}\n{{/if}}\n\n{{planPrompt}}\n\n{{#if suggestedAgents.length}}\n## Agents (Claude-specific)\nYou have access to the Agent tool for spawning subagents. Use these agents for this task:\n{{#each suggestedAgents}}\n- Use the **{{this}}** agent for specialized work in its domain.\n{{/each}}\n\nLaunch agents for independent subtasks to maximize parallelism.\nUse the main thread for coordination and integration.\n{{/if}}\n\n{{#if suggestedSkills.length}}\n## Skills\nInvoke these skills during execution:\n{{#each suggestedSkills}}\n- Run **/{{this}}** for specialized quality checks and procedures.\n{{/each}}\n{{/if}}\n\n{{#if suggestedPaths.length}}\nTarget paths: {{suggestedPaths | join ", "}}\n{{/if}}\n\nWorkspace: {{workspacePath}}\n\nIssue: {{issueIdentifier}}\nTitle: {{title}}\nDescription: {{description}}\n\n## Structured Input\nThe file `execution-payload.json` in the workspace contains the canonical structured data for this task.\nUse it as the source of truth for constraints, success criteria, execution intent, and plan details.\nIf there is any conflict between this prompt and the structured fields in the payload, prioritize the payload.\n\n{{#if validationItems.length}}\n## Pre-completion enforcement\nBefore reporting done, verify:\n{{#each validationItems}}\n- {{value}}\n{{/each}}\n{{/if}}\n',
190
190
  // src/agents/prompts/compile-execution-claude.stub.md
191
- "compile-execution-codex": '{{#if isReviewer}}\nRole: reviewer. Inspect and review the implementation critically.\n{{else}}\n{{#if isPlanner}}\nRole: planner. Analyze and prepare an execution plan.\n{{else}}\nRole: executor. Implement the required changes in the workspace.\n{{/if}}\n{{/if}}\n\n{{#if profileInstructions}}\n## Agent Profile\n{{profileInstructions}}\n{{/if}}\n\n{{#if capabilitiesManifest}}\n{{capabilitiesManifest}}\n{{/if}}\n\n{{#if skillContext}}\n{{skillContext}}\n{{/if}}\n\nIssue: {{issueIdentifier}}\nTitle: {{title}}\nDescription: {{description}}\nWorkspace: {{workspacePath}}\n\n{{planPrompt}}\n\n{{#if phases.length}}\n## Checkpoint Execution (Codex mode)\nExecute in strict phases. After each phase, verify outputs before proceeding.\n{{#each phases}}\n- **{{phaseName}}**: {{goal}}\n{{#if outputs.length}} Checkpoint: verify {{outputs | join ", "}} before next phase.{{/if}}\n{{/each}}\n{{else}}\n## Execution Order\nExecute steps in order. Verify each step\'s `doneWhen` criterion before proceeding.\n{{/if}}\n\n{{#if suggestedPaths.length}}\nTarget paths: {{suggestedPaths | join ", "}}\nFocus changes on these paths. Do not make unnecessary changes elsewhere.\n{{/if}}\n\n{{#if suggestedSkills.length}}\n## Skills\nInvoke these skills during execution:\n{{#each suggestedSkills}}\n- Run **/{{this}}** for specialized quality checks and procedures.\n{{/each}}\n{{/if}}\n\n{{#if validationItems.length}}\n## Pre-completion checks\nBefore reporting done, run:\n{{#each validationItems}}\n- {{value}}\n{{/each}}\n{{/if}}\n\n## Structured Input\nThe file `fifony-execution-payload.json` in the workspace contains the canonical structured data for this task.\nUse it as the source of truth for constraints, success criteria, execution intent, and plan details.\nIf there is any conflict between this prompt and the structured fields in the payload, prioritize the payload.\n\n## Output Format\n\n{{outputContract}}\n',
191
+ "compile-execution-codex": '{{#if isReviewer}}\nRole: reviewer. Inspect and review the implementation critically.\n{{else}}\n{{#if isPlanner}}\nRole: planner. Analyze and prepare an execution plan.\n{{else}}\nRole: executor. Implement the required changes in the workspace.\n{{/if}}\n{{/if}}\n\n{{#if profileInstructions}}\n## Agent Profile\n{{profileInstructions}}\n{{/if}}\n\n{{#if capabilitiesManifest}}\n{{capabilitiesManifest}}\n{{/if}}\n\n{{#if skillContext}}\n{{skillContext}}\n{{/if}}\n\nIssue: {{issueIdentifier}}\nTitle: {{title}}\nDescription: {{description}}\nWorkspace: {{workspacePath}}\n\n{{planPrompt}}\n\n{{#if phases.length}}\n## Checkpoint Execution (Codex mode)\nExecute in strict phases. After each phase, verify outputs before proceeding.\n{{#each phases}}\n- **{{phaseName}}**: {{goal}}\n{{#if outputs.length}} Checkpoint: verify {{outputs | join ", "}} before next phase.{{/if}}\n{{/each}}\n{{else}}\n## Execution Order\nExecute steps in order. Verify each step\'s `doneWhen` criterion before proceeding.\n{{/if}}\n\n{{#if suggestedPaths.length}}\nTarget paths: {{suggestedPaths | join ", "}}\nFocus changes on these paths. Do not make unnecessary changes elsewhere.\n{{/if}}\n\n{{#if suggestedSkills.length}}\n## Skills\nInvoke these skills during execution:\n{{#each suggestedSkills}}\n- Run **/{{this}}** for specialized quality checks and procedures.\n{{/each}}\n{{/if}}\n\n{{#if validationItems.length}}\n## Pre-completion checks\nBefore reporting done, run:\n{{#each validationItems}}\n- {{value}}\n{{/each}}\n{{/if}}\n\n## Structured Input\nThe file `execution-payload.json` in the workspace contains the canonical structured data for this task.\nUse it as the source of truth for constraints, success criteria, execution intent, and plan details.\nIf there is any conflict between this prompt and the structured fields in the payload, prioritize the payload.\n\n## Output Format\n\n{{outputContract}}\n',
192
192
  // src/agents/prompts/compile-execution-codex.stub.md
193
- "compile-review": "Review the work done for {{issueIdentifier}}.\n\nTitle: {{title}}\nDescription: {{description}}\nWorkspace: {{workspacePath}}\n\n{{#if planPrompt}}\n# Original Execution Plan\n\n{{planPrompt}}\n{{/if}}\n\n{{#if successCriteria.length}}\n# Success Criteria (evaluate against these)\n{{#each successCriteria}}\n- [ ] {{value}}\n{{/each}}\n{{/if}}\n\n{{#if deliverables.length}}\n# Expected Deliverables\n{{#each deliverables}}\n- [ ] {{value}}\n{{/each}}\n{{/if}}\n\n{{#if diffSummary}}\n# Changes Made (diff summary)\n```\n{{diffSummary}}\n```\n{{/if}}\n\n# Structured Context\nIf `fifony-execution-payload.json` exists in the workspace, read it for the canonical structured task data.\nUse the `successCriteria`, `constraints`, and `deliverables` fields as your evaluation checklist.\n\n# Review Instructions\n\n1. Verify each success criterion from the plan is met.\n2. Check that all expected deliverables are present.\n3. Review the diff for correctness, security issues, and code quality.\n4. Verify validation checks pass (run commands if specified in the plan).\n5. Check for unintended side effects or regressions.\n\nIf the work is acceptable, emit FIFONY_STATUS=done.\nIf rework is needed, emit FIFONY_STATUS=continue and provide actionable feedback in nextPrompt.\nIf the work is fundamentally broken, emit FIFONY_STATUS=blocked.\n",
193
+ "compile-review": "Review the work done for {{issueIdentifier}}.\n\nTitle: {{title}}\nDescription: {{description}}\nWorkspace: {{workspacePath}}\n{{#if images.length}}\n\n## Visual Evidence (screenshots attached to this issue)\n{{#each images}}\n- {{this}}\n{{/each}}\nCompare the implementation against these screenshots if they show expected behavior or bugs.\n{{/if}}\n\n{{#if planPrompt}}\n# Original Execution Plan\n\n{{planPrompt}}\n{{/if}}\n\n{{#if successCriteria.length}}\n# Success Criteria (evaluate against these)\n{{#each successCriteria}}\n- [ ] {{value}}\n{{/each}}\n{{/if}}\n\n{{#if deliverables.length}}\n# Expected Deliverables\n{{#each deliverables}}\n- [ ] {{value}}\n{{/each}}\n{{/if}}\n\n{{#if diffSummary}}\n# Changes Made (diff summary)\n```\n{{diffSummary}}\n```\n{{/if}}\n\n# Structured Context\nIf `execution-payload.json` exists in the workspace, read it for the canonical structured task data.\nUse the `successCriteria`, `constraints`, and `deliverables` fields as your evaluation checklist.\n\n# Review Instructions\n\n1. Verify each success criterion from the plan is met.\n2. Check that all expected deliverables are present.\n3. Review the diff for correctness, security issues, and code quality.\n4. Verify validation checks pass (run commands if specified in the plan).\n5. Check for unintended side effects or regressions.\n\nIf the work is acceptable, emit FIFONY_STATUS=done.\nIf rework is needed, emit FIFONY_STATUS=continue and provide actionable feedback in nextPrompt.\nIf the work is fundamentally broken, emit FIFONY_STATUS=blocked.\n",
194
194
  // src/agents/prompts/compile-review.stub.md
195
195
  "integrations-agency-agents": '---\nagent:\n providers:\n - provider: claude\n role: planner\n profile: agency-senior-project-manager\n - provider: codex\n role: executor\n profile: agency-senior-developer\n - provider: claude\n role: reviewer\n profile: agency-code-reviewer\ncodex:\n command: "codex"\nclaude:\n command: "claude"\n---\n\nUse local agency agent profiles discovered from workspace or home directories.\nWorkspace: {{workspaceRoot}}\n',
196
196
  // src/agents/prompts/integrations-agency-agents.stub.md
@@ -202,7 +202,7 @@ var PROMPT_TEMPLATES = {
202
202
  // src/agents/prompts/issue-enhancer-title.stub.md
203
203
  "issue-planner": 'You are a senior technical execution planner.\nProduce the best possible plan for the issue below, filling the JSON schema precisely.\n{{#if fast}}\n\nFAST MODE: Be brief and direct. Minimize reasoning depth.\n- 2-4 steps maximum. Skip optional fields (unknowns, risks, alternatives).\n- Focus only on: summary, steps, estimatedComplexity, suggestedPaths.\n{{/if}}\n\n{{#if availableCapabilities}}\n## Installed Capabilities (recommend from these lists)\n\n{{#if availableSkills.length}}\n### Skills\n{{#each availableSkills}}\n- **{{name}}**{{#if description}} \u2014 {{description}}{{/if}}{{#if whenToUse}} (Use when: {{whenToUse}}){{/if}}\n{{/each}}\n{{/if}}\n{{#if availableAgents.length}}\n### Agents\n{{#each availableAgents}}\n- **{{name}}**{{#if description}} \u2014 {{description}}{{/if}}{{#if whenToUse}} (Use when: {{whenToUse}}){{/if}}{{#if avoidIf}} (Avoid if: {{avoidIf}}){{/if}}\n{{/each}}\n{{/if}}\n{{#if availableCommands.length}}\n### Commands\n{{#each availableCommands}}\n- /{{name}}\n{{/each}}\n{{/if}}\n\nRecommend skills and agents ONLY from these lists. Do not invent names.\nOnly recommend when there is a concrete benefit \u2014 not everything needs skills or agents.\n{{/if}}\n\nIssue title: {{title}}\nIssue description: {{description}}\n{{#if images}}\nVisual evidence (attached screenshots for context):\n{{#each images}}\n- {{this}}\n{{/each}}\n{{/if}}\n{{#unless fast}}\n\nQuality rules:\n- Be concrete, not generic. No vague phrases like \'implement\' or \'improve\' without detail.\n- Break work into actionable steps (2-8 steps). Each step describes WHAT, not HOW.\n- Each step must have a clear \'doneWhen\' acceptance criterion.\n- Identify assumptions, constraints, unknowns, and risks.\n- For unknowns, specify what question needs answering and how to resolve it.\n- Suggest file paths that are likely relevant to the changes.\n\nComplexity estimation:\n- trivial: < 5 min, single-file cosmetic change\n- low: 5-15 min, small focused change\n- medium: 15-60 min, multi-file change with testing\n- high: > 1 hour, architectural change or new feature\n\nEffort suggestion:\n- low: simple fixes, no deep reasoning needed\n- medium: standard development work\n- high: complex architecture, security, or cross-cutting changes\n- Set per-role if different: planner, executor, reviewer\n{{/unless}}\n\n## Instructions\n\nYou are encouraged to explore the codebase \u2014 read files, search for patterns, inspect structure \u2014 to produce an informed plan. Use any tools available to you.\n\nAfter your analysis, you MUST output the final plan as a single JSON code block (```json ... ```).\nThe JSON block must be the LAST thing in your output. Any analysis or reasoning should come BEFORE it.\n\nIMPORTANT: Replace ALL placeholder values with real content specific to the issue above. Do NOT copy the example values literally \u2014 every field must contain actual plan content derived from the issue.\n\nUse these exact field names:\n\n```json\n{\n "summary": "<YOUR one-line summary here>",\n "estimatedComplexity": "trivial|low|medium|high",\n "steps": [\n {\n "step": 1,\n "action": "<YOUR concrete action here>",\n "files": ["<real/path/to/file.ts>"],\n "details": "<YOUR additional context>",\n "doneWhen": "<YOUR acceptance criterion>"\n }\n ],\n "assumptions": ["<YOUR assumptions>"],\n "constraints": ["<YOUR constraints>"],\n "unknowns": [\n { "question": "<YOUR question>", "whyItMatters": "<YOUR reason>", "howToResolve": "<YOUR approach>" }\n ],\n "successCriteria": ["<YOUR criteria>"],\n "risks": [\n { "risk": "<YOUR risk>", "impact": "<YOUR impact>", "mitigation": "<YOUR mitigation>" }\n ],\n "suggestedPaths": ["<real/path/to/relevant/file.ts>"],\n "suggestedSkills": ["<skill-name-from-list-above>"],\n "suggestedAgents": ["<agent-name-from-list-above>"],\n "suggestedEffort": { "default": "medium", "planner": "low", "executor": "medium", "reviewer": "medium" }\n}\n```\n',
204
204
  // src/agents/prompts/issue-planner.stub.md
205
- "issue-planner-refine": "You are a senior technical execution planner refining an existing plan based on user feedback.\n\n## Original Issue\nTitle: {{title}}\nDescription: {{description}}\n\n## Current Plan (JSON)\n```json\n{{currentPlan}}\n```\n\n## User Feedback\n{{feedback}}\n\n## Instructions\n\nRevise the plan above to address the user's feedback precisely.\n\nRules:\n- Keep all parts of the plan that are NOT affected by the feedback unchanged.\n- Only modify, add, or remove elements that the feedback specifically requests.\n- Preserve the same JSON schema structure as the current plan.\n- Maintain step numbering consistency after changes.\n- If feedback asks to add steps, insert them in the logical position and renumber.\n- If feedback asks to remove steps, renumber remaining steps sequentially.\n- Update the summary if the overall direction changed.\n- Re-evaluate estimatedComplexity if the scope changed significantly.\n- Update suggestedPaths, suggestedSkills, and suggestedAgents if affected by the changes.\n\nReturn strict JSON. No text outside JSON.\n",
205
+ "issue-planner-refine": "You are a senior technical execution planner refining an existing plan based on user feedback.\n\n## Original Issue\nTitle: {{title}}\nDescription: {{description}}\n{{#if images}}\n\n## Visual Evidence\n{{#each images}}\n- {{this}}\n{{/each}}\n{{/if}}\n\n## Current Plan (JSON)\n```json\n{{currentPlan}}\n```\n\n## User Feedback\n{{feedback}}\n\n## Instructions\n\nRevise the plan above to address the user's feedback precisely.\n\nRules:\n- Keep all parts of the plan that are NOT affected by the feedback unchanged.\n- Only modify, add, or remove elements that the feedback specifically requests.\n- Preserve the same JSON schema structure as the current plan.\n- Maintain step numbering consistency after changes.\n- If feedback asks to add steps, insert them in the logical position and renumber.\n- If feedback asks to remove steps, renumber remaining steps sequentially.\n- Update the summary if the overall direction changed.\n- Re-evaluate estimatedComplexity if the scope changed significantly.\n- Update suggestedPaths, suggestedSkills, and suggestedAgents if affected by the changes.\n\nYou may explore the codebase to inform your revisions. After your analysis, return the revised plan as a single JSON code block (```json ... ```) as the LAST thing in your output.\n",
206
206
  // src/agents/prompts/issue-planner-refine.stub.md
207
207
  "mcp-integrate-client": "Integrate {{client}} with the local fifony MCP server.\n\nGoal: {{goal}}\n\n{{integrationGuide}}\n\nUse the available fifony resources and tools instead of inventing your own persistence model.\n",
208
208
  // src/agents/prompts/mcp-integrate-client.stub.md
@@ -212,7 +212,7 @@ var PROMPT_TEMPLATES = {
212
212
  // src/agents/prompts/mcp-issue.stub.md
213
213
  "mcp-review-workflow": "Review the pipeline configuration for this fifony workspace as {{provider}}.\n\nWorkspace: {{workspaceRoot}}\n\nFocus on:\n- provider orchestration quality (plan/execute/review stages)\n- hooks safety (beforeRun, afterRun, afterCreate, beforeRemove)\n- prompt clarity\n- issue lifecycle correctness\n- what an MCP client needs in order to integrate cleanly\n",
214
214
  // src/agents/prompts/mcp-review-workflow.stub.md
215
- "merge-conflict-resolver": "You are resolving git merge conflicts in a software project.\n\n## Context\n\nIssue: {{issueIdentifier}} \u2014 {{title}}\n{{#if description}}\nDescription: {{description}}\n{{/if}}\nMerging branch `{{featureBranch}}` into `{{baseBranch}}`.\n\n## Conflicting Files\n\nThe following files have conflict markers (`<<<<<<<`, `=======`, `>>>>>>>`) that you must resolve:\n\n{{#each conflictFiles}}\n- {{this}}\n{{/each}}\n\n## Instructions\n\n1. Read each conflicting file and understand the intent of BOTH sides.\n2. Resolve the conflict markers by choosing the correct combination of changes. Prefer keeping both sides' intent when possible.\n3. After resolving, stage each file with `git add <file>`.\n4. Do NOT commit \u2014 the merge commit will be created automatically after you finish.\n5. Do NOT modify files that are not in the conflict list.\n6. Verify there are no remaining conflict markers (`<<<<<<<`) in any resolved file.\n",
215
+ "merge-conflict-resolver": 'You are resolving git merge conflicts in a software project.\n\n## Context\n\nIssue: {{issueIdentifier}} \u2014 {{title}}\n{{#if description}}\nDescription: {{description}}\n{{/if}}\nMerging branch `{{featureBranch}}` into `{{baseBranch}}`.\n\n## Conflicting Files\n\nThe following files have conflict markers (`<<<<<<<`, `=======`, `>>>>>>>`) that you must resolve:\n\n{{#each conflictFiles}}\n- {{this}}\n{{/each}}\n\n## Instructions\n\n1. Read each conflicting file and understand the intent of BOTH sides.\n2. Resolve ALL conflict markers (`<<<<<<<`, `=======`, `>>>>>>>`) by choosing the correct combination of changes. Prefer keeping both sides\' intent when possible.\n3. CRITICAL: Before staging, run `grep -n "^<<<<<<<" <file>` on EACH file to verify zero conflict markers remain. If any markers remain, fix them first.\n4. After verifying each file is clean, stage with `git add <file>`.\n5. Do NOT commit \u2014 the merge commit will be created automatically after you finish.\n6. Do NOT modify files that are not in the conflict list.\n7. Do NOT use `git add .` or `git add -A` \u2014 stage only the conflicting files listed above.\n',
216
216
  // src/agents/prompts/merge-conflict-resolver.stub.md
217
217
  "project-analysis": `You are analyzing a software project to help configure an AI-powered development assistant.
218
218
 
@@ -285,4 +285,4 @@ export {
285
285
  repairTruncatedJson,
286
286
  renderPrompt
287
287
  };
288
- //# sourceMappingURL=chunk-EPY5TTQK.js.map
288
+ //# sourceMappingURL=chunk-2CVTK5F2.js.map
@@ -1,19 +1,19 @@
1
1
  import {
2
2
  computeDiffStats,
3
+ removeTestWorkspace,
3
4
  syncIssueDiffStatsToStore
4
- } from "./chunk-O3FGX4J6.js";
5
- import {
6
- logger
7
- } from "./chunk-DVU3CXWA.js";
5
+ } from "./chunk-NB44PCD2.js";
8
6
  import {
9
7
  isoWeek,
10
8
  now
11
- } from "./chunk-EPY5TTQK.js";
9
+ } from "./chunk-2CVTK5F2.js";
12
10
  import {
13
11
  S3DB_ISSUE_RESOURCE,
14
- TARGET_ROOT,
15
12
  TERMINAL_STATES
16
13
  } from "./chunk-37N5OFHM.js";
14
+ import {
15
+ logger
16
+ } from "./chunk-DVU3CXWA.js";
17
17
 
18
18
  // src/agents/failure-analyzer.ts
19
19
  function extractFilePaths(output) {
@@ -311,22 +311,19 @@ function markAllEventsDirty(ids) {
311
311
 
312
312
  // src/persistence/plugins/issue-state-machine.ts
313
313
  import { existsSync, readdirSync, readFileSync, statSync } from "fs";
314
- import { execSync } from "child_process";
315
314
  import { join } from "path";
316
315
  var fsmEventEmitter = null;
317
316
  function setFsmEventEmitter(emitter) {
318
317
  fsmEventEmitter = emitter;
319
318
  }
320
- function autoRevertTestSquash(issue) {
319
+ function cleanupActiveTestWorkspace(issue) {
321
320
  if (!issue.testApplied) return;
322
321
  try {
323
- execSync("git reset --hard HEAD", { cwd: TARGET_ROOT, stdio: "pipe", timeout: 15e3 });
324
- execSync("git clean -fd", { cwd: TARGET_ROOT, stdio: "pipe", timeout: 15e3 });
325
- logger.info({ issueId: issue.id }, "[FSM] Auto-reverted test squash from TARGET_ROOT");
322
+ removeTestWorkspace(issue);
323
+ logger.info({ issueId: issue.id }, "[FSM] Removed isolated test workspace");
326
324
  } catch (err) {
327
- logger.warn({ err: String(err), issueId: issue.id }, "[FSM] Failed to auto-revert test squash");
325
+ logger.warn({ err: String(err), issueId: issue.id }, "[FSM] Failed to remove isolated test workspace");
328
326
  }
329
- issue.testApplied = false;
330
327
  }
331
328
  function emitFsmEvent(issueId, kind, message) {
332
329
  if (fsmEventEmitter) {
@@ -335,6 +332,25 @@ function emitFsmEvent(issueId, kind, message) {
335
332
  } catch {
336
333
  }
337
334
  }
335
+ if (kind === "state") {
336
+ import("./web-push-CRVDJKWR.js").then(({ sendPushToAll, isWebPushReady, SETTING_ID_PUSH_SUBSCRIPTIONS }) => {
337
+ if (!isWebPushReady()) return;
338
+ import("./settings-NGY33WQE.js").then(({ persistSetting }) => {
339
+ sendPushToAll(
340
+ { title: "fifony", body: message, tag: issueId, url: "/kanban" },
341
+ async (subs) => {
342
+ await persistSetting(SETTING_ID_PUSH_SUBSCRIPTIONS, subs, {
343
+ scope: "system",
344
+ source: "system"
345
+ });
346
+ }
347
+ ).catch(() => {
348
+ });
349
+ }).catch(() => {
350
+ });
351
+ }).catch(() => {
352
+ });
353
+ }
338
354
  }
339
355
  var enqueueFn = null;
340
356
  function setEnqueueFn(fn) {
@@ -447,7 +463,7 @@ var issueStateMachineConfig = {
447
463
  onEnterPlanning: async (context, _event, _machine) => {
448
464
  const issue = resolveIssue(context);
449
465
  if (issue) {
450
- autoRevertTestSquash(issue);
466
+ cleanupActiveTestWorkspace(issue);
451
467
  issue.planningStatus = "idle";
452
468
  issue.planningError = void 0;
453
469
  issue.nextRetryAt = void 0;
@@ -476,7 +492,7 @@ var issueStateMachineConfig = {
476
492
  onEnterQueued: async (context, event, _machine) => {
477
493
  const issue = resolveIssue(context);
478
494
  if (issue) {
479
- autoRevertTestSquash(issue);
495
+ cleanupActiveTestWorkspace(issue);
480
496
  if (event === "REQUEUE") {
481
497
  const feedback = typeof context.note === "string" ? context.note : void 0;
482
498
  if (feedback) issue.lastError = feedback;
@@ -616,7 +632,7 @@ var issueStateMachineConfig = {
616
632
  const week = isoWeek();
617
633
  const reason = typeof context.reason === "string" ? context.reason : typeof context.note === "string" ? context.note : void 0;
618
634
  if (issue) {
619
- autoRevertTestSquash(issue);
635
+ cleanupActiveTestWorkspace(issue);
620
636
  issue.completedAt = ts;
621
637
  issue.terminalWeek = week;
622
638
  issue.nextRetryAt = void 0;
@@ -875,4 +891,4 @@ export {
875
891
  canTransitionIssue,
876
892
  visualizeStateMachine
877
893
  };
878
- //# sourceMappingURL=chunk-UXXUTDGV.js.map
894
+ //# sourceMappingURL=chunk-H5N7O5NP.js.map
@@ -0,0 +1,104 @@
1
+ import {
2
+ logger
3
+ } from "./chunk-DVU3CXWA.js";
4
+
5
+ // src/domains/web-push.ts
6
+ import webpush from "web-push";
7
+ var vapidKeys = null;
8
+ var vapidConfigured = false;
9
+ var subscriptions = /* @__PURE__ */ new Map();
10
+ var SETTING_ID_VAPID_PUBLIC = "system.push.vapidPublicKey";
11
+ var SETTING_ID_VAPID_PRIVATE = "system.push.vapidPrivateKey";
12
+ var SETTING_ID_PUSH_SUBSCRIPTIONS = "system.push.subscriptions";
13
+ var SETTING_ID_PUSH_CONTACT = "system.push.contact";
14
+ function getVapidPublicKey() {
15
+ return vapidKeys?.publicKey ?? null;
16
+ }
17
+ function isWebPushReady() {
18
+ return vapidConfigured && vapidKeys !== null;
19
+ }
20
+ async function initWebPush(loadSetting, saveSetting) {
21
+ try {
22
+ let publicKey = await loadSetting(SETTING_ID_VAPID_PUBLIC);
23
+ let privateKey = await loadSetting(SETTING_ID_VAPID_PRIVATE);
24
+ if (!publicKey || !privateKey) {
25
+ const generated = webpush.generateVAPIDKeys();
26
+ publicKey = generated.publicKey;
27
+ privateKey = generated.privateKey;
28
+ await saveSetting(SETTING_ID_VAPID_PUBLIC, publicKey, "system");
29
+ await saveSetting(SETTING_ID_VAPID_PRIVATE, privateKey, "system");
30
+ logger.info("[WebPush] Generated and saved new VAPID keys");
31
+ }
32
+ const contact = await loadSetting(SETTING_ID_PUSH_CONTACT) || "mailto:fifony@localhost";
33
+ webpush.setVapidDetails(contact, publicKey, privateKey);
34
+ vapidKeys = { publicKey, privateKey };
35
+ vapidConfigured = true;
36
+ const saved = await loadSetting(SETTING_ID_PUSH_SUBSCRIPTIONS);
37
+ if (Array.isArray(saved)) {
38
+ for (const sub of saved) {
39
+ if (sub.endpoint) subscriptions.set(sub.endpoint, sub);
40
+ }
41
+ logger.info({ count: subscriptions.size }, "[WebPush] Restored push subscriptions");
42
+ }
43
+ logger.info("[WebPush] Initialized");
44
+ } catch (err) {
45
+ logger.warn({ err: String(err) }, "[WebPush] Failed to initialize \u2014 push notifications disabled");
46
+ }
47
+ }
48
+ async function addSubscription(sub, persistSubscriptions) {
49
+ subscriptions.set(sub.endpoint, sub);
50
+ await persistSubscriptions([...subscriptions.values()]);
51
+ logger.info({ endpoint: sub.endpoint.slice(0, 60) }, "[WebPush] Subscription added");
52
+ }
53
+ async function removeSubscription(endpoint, persistSubscriptions) {
54
+ subscriptions.delete(endpoint);
55
+ await persistSubscriptions([...subscriptions.values()]);
56
+ logger.debug({ endpoint: endpoint.slice(0, 60) }, "[WebPush] Subscription removed");
57
+ }
58
+ function getSubscriptionCount() {
59
+ return subscriptions.size;
60
+ }
61
+ async function sendPushToAll(payload, persistSubscriptions) {
62
+ if (!vapidConfigured || subscriptions.size === 0) return 0;
63
+ const jsonPayload = JSON.stringify(payload);
64
+ let sent = 0;
65
+ let removed = 0;
66
+ const results = await Promise.allSettled(
67
+ [...subscriptions.values()].map(async (sub) => {
68
+ try {
69
+ await webpush.sendNotification(sub, jsonPayload, { TTL: 3600 });
70
+ sent++;
71
+ } catch (err) {
72
+ if (err?.statusCode === 410 || err?.statusCode === 404) {
73
+ subscriptions.delete(sub.endpoint);
74
+ removed++;
75
+ } else {
76
+ logger.debug({ err: String(err), endpoint: sub.endpoint.slice(0, 60) }, "[WebPush] Failed to send");
77
+ }
78
+ }
79
+ })
80
+ );
81
+ if (removed > 0) {
82
+ await persistSubscriptions([...subscriptions.values()]);
83
+ logger.debug({ removed }, "[WebPush] Cleaned stale subscriptions");
84
+ }
85
+ if (sent > 0) {
86
+ logger.debug({ sent, total: subscriptions.size, tag: payload.tag }, "[WebPush] Push delivered");
87
+ }
88
+ return sent;
89
+ }
90
+
91
+ export {
92
+ SETTING_ID_VAPID_PUBLIC,
93
+ SETTING_ID_VAPID_PRIVATE,
94
+ SETTING_ID_PUSH_SUBSCRIPTIONS,
95
+ SETTING_ID_PUSH_CONTACT,
96
+ getVapidPublicKey,
97
+ isWebPushReady,
98
+ initWebPush,
99
+ addSubscription,
100
+ removeSubscription,
101
+ getSubscriptionCount,
102
+ sendPushToAll
103
+ };
104
+ //# sourceMappingURL=chunk-I2UHVKHS.js.map