iterate 0.2.7 → 0.3.0
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.
- package/README.md +86 -76
- package/THIRD_PARTY_NOTICES.md +55 -0
- package/bin/iterate.js +11 -3
- package/dist/api-url-B6404M82.mjs +17 -0
- package/dist/api-url-B6404M82.mjs.map +1 -0
- package/dist/app-ref-BipL0feU.mjs +35 -0
- package/dist/app-ref-BipL0feU.mjs.map +1 -0
- package/dist/app-ref-C1CrgXqX.mjs +7 -0
- package/dist/app-ref-C1CrgXqX.mjs.map +1 -0
- package/dist/app-ref-DYai_om1.mjs +7 -0
- package/dist/app-ref-DYai_om1.mjs.map +1 -0
- package/dist/cli-D0c-pDL_.mjs +1010 -0
- package/dist/cli-D0c-pDL_.mjs.map +1 -0
- package/dist/client.d.ts +3 -0
- package/dist/client.mjs +4 -0
- package/dist/cloudflare-BTm90gQ4.mjs +951 -0
- package/dist/cloudflare-BTm90gQ4.mjs.map +1 -0
- package/dist/contract-s4FW4eES.mjs +309 -0
- package/dist/contract-s4FW4eES.mjs.map +1 -0
- package/dist/document-review/index.d.ts +5 -0
- package/dist/document-review/types.d.ts +107 -0
- package/dist/document-review.mjs +7015 -0
- package/dist/document-review.mjs.map +1 -0
- package/dist/durable-object-processor-durability-CNsTjAJS.mjs +205 -0
- package/dist/durable-object-processor-durability-CNsTjAJS.mjs.map +1 -0
- package/dist/idempotency-DleloJNt.mjs +28 -0
- package/dist/idempotency-DleloJNt.mjs.map +1 -0
- package/dist/index.mjs +1 -1
- package/dist/itx/api-url.d.ts +6 -0
- package/dist/itx/itx-node-client.d.ts +65 -0
- package/dist/itx/itx-session.d.ts +215 -0
- package/dist/itx/owned-rpc-session.d.ts +14 -0
- package/dist/itx/query-client.d.ts +10 -0
- package/dist/itx-api.generated.d.ts +6195 -0
- package/dist/itx-session-sjud8GiT.mjs +534 -0
- package/dist/itx-session-sjud8GiT.mjs.map +1 -0
- package/dist/live-state-BJNqOwFw.mjs +299 -0
- package/dist/live-state-BJNqOwFw.mjs.map +1 -0
- package/dist/next/api.d.ts +479 -0
- package/dist/next/api.mjs +0 -0
- package/dist/next/app-server.d.ts +44 -0
- package/dist/next/app-server.mjs +479 -0
- package/dist/next/app-server.mjs.map +1 -0
- package/dist/next/app-session.d.ts +49 -0
- package/dist/next/app-session.mjs +238 -0
- package/dist/next/app-session.mjs.map +1 -0
- package/dist/next/app.d.ts +29 -0
- package/dist/next/app.mjs +141 -0
- package/dist/next/app.mjs.map +1 -0
- package/dist/next/client/live-state.d.ts +63 -0
- package/dist/next/client/oauth.d.ts +12 -0
- package/dist/next/client/react.d.ts +109 -0
- package/dist/next/client/socket.d.ts +6 -0
- package/dist/next/client.mjs +156 -0
- package/dist/next/client.mjs.map +1 -0
- package/dist/next/expression.d.ts +146 -0
- package/dist/next/expression.mjs +399 -0
- package/dist/next/expression.mjs.map +1 -0
- package/dist/next/lib.d.ts +56 -0
- package/dist/next/lib.mjs +199 -0
- package/dist/next/lib.mjs.map +1 -0
- package/dist/next/oauth-scopes.d.ts +32 -0
- package/dist/next/oauth-scopes.mjs +40 -0
- package/dist/next/oauth-scopes.mjs.map +1 -0
- package/dist/next/oauth.mjs +29 -0
- package/dist/next/oauth.mjs.map +1 -0
- package/dist/next/principal.d.ts +64 -0
- package/dist/next/principal.mjs +98 -0
- package/dist/next/principal.mjs.map +1 -0
- package/dist/next/project-ingress.d.ts +37 -0
- package/dist/next/project-ingress.mjs +75 -0
- package/dist/next/project-ingress.mjs.map +1 -0
- package/dist/next/react.mjs +285 -0
- package/dist/next/react.mjs.map +1 -0
- package/dist/next/sdk/auth.d.ts +5 -0
- package/dist/next/sdk/index.d.ts +112 -0
- package/dist/next/sdk.mjs +139 -0
- package/dist/next/sdk.mjs.map +1 -0
- package/dist/next/stream/processor.d.ts +378 -0
- package/dist/next/stream/processor.mjs +582 -0
- package/dist/next/stream/processor.mjs.map +1 -0
- package/dist/next/stream/run.d.ts +58 -0
- package/dist/next/stream/run.mjs +40 -0
- package/dist/next/stream/run.mjs.map +1 -0
- package/dist/next-node.d.ts +15 -0
- package/dist/next-node.mjs +51 -0
- package/dist/next-node.mjs.map +1 -0
- package/dist/node.d.ts +3 -0
- package/dist/node.mjs +185 -0
- package/dist/node.mjs.map +1 -0
- package/dist/processor-host-capabilities-BMFH3KTM.mjs +56 -0
- package/dist/processor-host-capabilities-BMFH3KTM.mjs.map +1 -0
- package/dist/processors/cloudflare.d.ts +3 -0
- package/dist/processors/durable-object-processor-durability.d.ts +79 -0
- package/dist/processors/event-consumption-metrics.d.ts +82 -0
- package/dist/processors/idempotency.d.ts +13 -0
- package/dist/processors/index.d.ts +12 -0
- package/dist/processors/processor-contracts.d.ts +342 -0
- package/dist/processors/processor-facet.d.ts +186 -0
- package/dist/processors/processor-host-capabilities.d.ts +60 -0
- package/dist/processors/prompt-sections.d.ts +17 -0
- package/dist/processors/rpc-types.d.ts +515 -0
- package/dist/processors/schemas.d.ts +102 -0
- package/dist/processors/stream-handle.d.ts +45 -0
- package/dist/processors/stream-processor-keepalive.d.ts +95 -0
- package/dist/processors/stream-processor-registry.d.ts +233 -0
- package/dist/processors/stream-processor-runner.d.ts +289 -0
- package/dist/processors/stream-processor.d.ts +339 -0
- package/dist/processors/stream-runtime-metrics.d.ts +107 -0
- package/dist/processors/testing.d.ts +302 -0
- package/dist/processors-BoNyeBfQ.mjs +10 -0
- package/dist/processors-BoNyeBfQ.mjs.map +1 -0
- package/dist/processors-cloudflare.mjs +3 -0
- package/dist/processors-testing.mjs +435 -0
- package/dist/processors-testing.mjs.map +1 -0
- package/dist/processors.mjs +52 -0
- package/dist/processors.mjs.map +1 -0
- package/dist/protocol-DnK_f2m6.mjs +251 -0
- package/dist/protocol-DnK_f2m6.mjs.map +1 -0
- package/dist/sdk/capnweb/index.d.ts +2 -0
- package/dist/sdk/capnweb/live-state/compact.d.ts +5 -0
- package/dist/sdk/capnweb/live-state/diff.d.ts +41 -0
- package/dist/sdk/capnweb/live-state/engine.d.ts +44 -0
- package/dist/sdk/capnweb/live-state/index.d.ts +41 -0
- package/dist/sdk/capnweb/live-state/protocol.d.ts +87 -0
- package/dist/sdk/capnweb/live-state/retain.d.ts +23 -0
- package/dist/sdk/capnweb/live-state/store.d.ts +20 -0
- package/dist/sdk/capnweb/live-state/types.d.ts +11 -0
- package/dist/sdk/capnweb/react.d.ts +45 -0
- package/dist/sdk/capnweb/react.mjs +316 -0
- package/dist/sdk/capnweb/react.mjs.map +1 -0
- package/dist/sdk/capnweb.mjs +4 -0
- package/dist/sdk/itx/react.d.ts +191 -0
- package/dist/sdk/itx/react.mjs +383 -0
- package/dist/sdk/itx/react.mjs.map +1 -0
- package/dist/sdk-DMB-IM11.mjs +933 -0
- package/dist/sdk-DMB-IM11.mjs.map +1 -0
- package/dist/sdk.d.ts +339 -0
- package/dist/sdk.mjs +2 -0
- package/dist/serve-itx.d.ts +46 -0
- package/dist/starter-apps/flake-dashboard/app-ref.d.ts +31 -0
- package/dist/starter-apps/flake-dashboard/configured-worker.mjs +1055 -0
- package/dist/starter-apps/flake-dashboard/configured-worker.mjs.map +1 -0
- package/dist/starter-apps/flake-dashboard/contract.d.ts +4839 -0
- package/dist/starter-apps/flake-dashboard/contract.mjs +2 -0
- package/dist/starter-apps/flake-dashboard/index.d.ts +17 -0
- package/dist/starter-apps/flake-dashboard/index.mjs +56 -0
- package/dist/starter-apps/flake-dashboard/index.mjs.map +1 -0
- package/dist/starter-apps/flake-dashboard/worker.d.ts +4607 -0
- package/dist/starter-apps/github-ai-linter/ai-linter.d.ts +8914 -0
- package/dist/starter-apps/github-ai-linter/configured-worker.mjs +17987 -0
- package/dist/starter-apps/github-ai-linter/configured-worker.mjs.map +1 -0
- package/dist/starter-apps/github-ai-linter/contract.d.ts +9193 -0
- package/dist/starter-apps/github-ai-linter/index.d.ts +10 -0
- package/dist/starter-apps/github-ai-linter/index.mjs +36 -0
- package/dist/starter-apps/github-ai-linter/index.mjs.map +1 -0
- package/dist/starter-apps/github-ai-linter/prompt.d.ts +13 -0
- package/dist/starter-apps/github-ai-linter/review-bot.d.ts +808 -0
- package/dist/starter-apps/github-ai-linter/rules.d.ts +34 -0
- package/dist/starter-apps/github-ai-linter/worker-ref.d.ts +19 -0
- package/dist/starter-apps/github-ai-linter/worker.d.ts +19 -0
- package/dist/starter-apps/github-ai-linter/worker.mjs +947 -0
- package/dist/starter-apps/github-ai-linter/worker.mjs.map +1 -0
- package/dist/starter-apps/guestbook/app-ref.d.ts +27 -0
- package/dist/starter-apps/guestbook/client.d.ts +7 -0
- package/dist/starter-apps/guestbook/client.mjs +59 -0
- package/dist/starter-apps/guestbook/configured-worker.mjs +205 -0
- package/dist/starter-apps/guestbook/configured-worker.mjs.map +1 -0
- package/dist/starter-apps/guestbook/index.d.ts +9 -0
- package/dist/starter-apps/guestbook/index.mjs +31 -0
- package/dist/starter-apps/guestbook/index.mjs.map +1 -0
- package/dist/starter-apps/guestbook/processor.d.ts +2267 -0
- package/dist/starter-apps/guestbook/worker.d.ts +26 -0
- package/dist/starter-apps/guestbook/worker.mjs +191 -0
- package/dist/starter-apps/guestbook/worker.mjs.map +1 -0
- package/dist/starter-apps/media/configured-worker.mjs +577 -0
- package/dist/starter-apps/media/configured-worker.mjs.map +1 -0
- package/dist/starter-apps/media/index.mjs +36 -0
- package/dist/starter-apps/media/index.mjs.map +1 -0
- package/dist/starter-apps/media/ref.mjs +20 -0
- package/dist/starter-apps/media/ref.mjs.map +1 -0
- package/dist/starter-apps/media/worker.mjs +579 -0
- package/dist/starter-apps/media/worker.mjs.map +1 -0
- package/dist/starter-apps/notes/configured-worker.mjs +6134 -0
- package/dist/starter-apps/notes/configured-worker.mjs.map +1 -0
- package/dist/starter-apps/notes/index.mjs +23 -0
- package/dist/starter-apps/notes/index.mjs.map +1 -0
- package/dist/starter-apps/notes/ref.mjs +21 -0
- package/dist/starter-apps/notes/ref.mjs.map +1 -0
- package/dist/starter-apps/notes/worker.mjs +427 -0
- package/dist/starter-apps/notes/worker.mjs.map +1 -0
- package/dist/starter-apps/todo/client.mjs +59 -0
- package/dist/starter-apps/todo/configured-worker.mjs +2864 -0
- package/dist/starter-apps/todo/configured-worker.mjs.map +1 -0
- package/dist/starter-apps/todo/index.d.ts +8 -0
- package/dist/starter-apps/todo/index.mjs +29 -0
- package/dist/starter-apps/todo/index.mjs.map +1 -0
- package/dist/stream-processor-keepalive-DAQTP6m3.mjs +2082 -0
- package/dist/stream-processor-keepalive-DAQTP6m3.mjs.map +1 -0
- package/dist/usingCtx-inzbY1Qz.mjs +57 -0
- package/dist/usingCtx-mZx5nsAW.mjs +11800 -0
- package/dist/usingCtx-mZx5nsAW.mjs.map +1 -0
- package/dist/worker-ref-DZxPDmb_.mjs +390 -0
- package/dist/worker-ref-DZxPDmb_.mjs.map +1 -0
- package/menubar/Iterate.entitlements +12 -0
- package/menubar/Iterate.swift +914 -0
- package/menubar/IterateIcon.swift +145 -0
- package/menubar/README.md +28 -0
- package/menubar/build-menubar-app.sh +59 -0
- package/package.json +235 -18
- package/dist/cli-DMS4kJph.mjs +0 -868
- package/dist/cli-DMS4kJph.mjs.map +0 -1
- package/dist/config-DtnR7Lv7.mjs +0 -170
- package/dist/config-DtnR7Lv7.mjs.map +0 -1
- package/dist/index.d.mts.map +0 -1
- package/dist/stream-tui/agent-chat-terminal.d.mts +0 -1
- package/dist/stream-tui/agent-chat-terminal.mjs +0 -933
- package/dist/stream-tui/agent-chat-terminal.mjs.map +0 -1
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { t as _usingCtx } from "../../usingCtx-inzbY1Qz.mjs";
|
|
2
|
+
import { notesRepoPath, notesWorkspacePath } from "./ref.mjs";
|
|
3
|
+
import { t as notesWorkerRef } from "../../app-ref-C1CrgXqX.mjs";
|
|
4
|
+
//#region src/starter-apps/notes/index.ts
|
|
5
|
+
const NotesApp = { create(env) {
|
|
6
|
+
return { async processEvent(event) {
|
|
7
|
+
try {
|
|
8
|
+
var _usingCtx$1 = _usingCtx();
|
|
9
|
+
if (event.path !== "/workspaces/notes") return;
|
|
10
|
+
if (!event.type.startsWith("events.iterate.com/notes/")) return;
|
|
11
|
+
const project = _usingCtx$1.u(await env.ITX.get());
|
|
12
|
+
await _usingCtx$1.u(project.workers.get(notesWorkerRef)).syncEvent(event);
|
|
13
|
+
} catch (_) {
|
|
14
|
+
_usingCtx$1.e = _;
|
|
15
|
+
} finally {
|
|
16
|
+
_usingCtx$1.d();
|
|
17
|
+
}
|
|
18
|
+
} };
|
|
19
|
+
} };
|
|
20
|
+
//#endregion
|
|
21
|
+
export { NotesApp, notesRepoPath, notesWorkerRef, notesWorkspacePath };
|
|
22
|
+
|
|
23
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../../../src/starter-apps/notes/index.ts"],"sourcesContent":["// Project-worker glue for the NotesApp: fan committed events on the notes\n// workspace's stream into the app's Durable Object (guestbook/media shape —\n// note volume doesn't warrant a wake-processor subscription). No capability\n// mount: notes are ordinary workspace documents, and agents find them with\n// glob/readFiles like any other file (convergence decision D6).\nimport type { DynamicWorkerCapability, ItxBinding, StreamEvent } from \"../../sdk.ts\";\nimport { notesWorkerRef, notesWorkspacePath } from \"./app-ref.ts\";\nimport type { NotesApp as NotesWorker } from \"./worker.ts\";\n\nexport { notesRepoPath, notesWorkerRef, notesWorkspacePath } from \"./app-ref.ts\";\n\nexport const NotesApp = {\n create(env: { ITX: Pick<ItxBinding, \"get\"> }) {\n return {\n async processEvent(event: StreamEvent): Promise<void> {\n if (event.path !== notesWorkspacePath) return;\n // Only the notes vocabulary wakes the app — the workspace's own\n // lifecycle events (created/configured) and typing ephemera are not\n // consumed by the processor, so skipping them here saves a dial.\n if (!event.type.startsWith(\"events.iterate.com/notes/\")) return;\n using project = await env.ITX.get();\n using worker = project.workers.get(notesWorkerRef) as DynamicWorkerCapability<\n Pick<NotesWorker, \"syncEvent\">\n >;\n await worker.syncEvent(event);\n },\n };\n },\n};\n"],"mappings":";;;;AAWA,MAAa,WAAW,EACtB,OAAO,KAAuC;CAC5C,OAAO,EACL,MAAM,aAAa,OAAmC;;;GACpD,IAAI,MAAM,SAAA,qBAA6B;GAIvC,IAAI,CAAC,MAAM,KAAK,WAAW,2BAA2B,GAAG;GACzD,MAAM,UAAA,YAAA,EAAU,MAAM,IAAI,IAAI,IAAI,CAAA;GAIlC,MAAA,YAAA,EAHe,QAAQ,QAAQ,IAAI,cAAc,CAGtC,CAAC,CAAC,UAAU,KAAK;;;;;;CAC9B,EACF;AACF,EACF"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
//#region src/starter-apps/notes/ref.ts
|
|
2
|
+
const notesWorkspacePath = "/workspaces/notes";
|
|
3
|
+
const notesRepoPath = "/repos/notes";
|
|
4
|
+
const notesWorkerRef = {
|
|
5
|
+
className: "NotesApp",
|
|
6
|
+
durableWorkerKey: "app-notes-stream",
|
|
7
|
+
path: "/",
|
|
8
|
+
source: { createWorker: {
|
|
9
|
+
entryPoint: "node_modules/iterate/dist/starter-apps/notes/configured-worker.mjs",
|
|
10
|
+
files: {
|
|
11
|
+
include: ["package.json"],
|
|
12
|
+
repoPath: "/repos/config",
|
|
13
|
+
type: "repo"
|
|
14
|
+
}
|
|
15
|
+
} },
|
|
16
|
+
type: "stateful"
|
|
17
|
+
};
|
|
18
|
+
//#endregion
|
|
19
|
+
export { notesRepoPath, notesWorkerRef, notesWorkspacePath };
|
|
20
|
+
|
|
21
|
+
//# sourceMappingURL=ref.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ref.mjs","names":[],"sources":["../../../src/starter-apps/notes/ref.ts"],"sourcesContent":["// The NotesApp worker ref as a dependency-free literal: the mobile app (a\n// plain-node tsconfig with no Cloudflare lib types) needs the ref without\n// pulling in sdk.ts. app-ref.ts re-exports this with the satisfies check\n// that keeps it honest against StatefulDynamicWorkerRef.\n//\n// Convergence model (tasks/mobile-notes.md, grill session 2): notes are\n// markdown files in the dedicated notes REPO, written through the notes\n// WORKSPACE; note facts ride the workspace's own stream path.\nexport const notesWorkspacePath = \"/workspaces/notes\";\nexport const notesRepoPath = \"/repos/notes\";\n\nexport const notesWorkerRef = {\n className: \"NotesApp\",\n durableWorkerKey: \"app-notes-stream\",\n path: \"/\",\n source: {\n createWorker: {\n entryPoint: \"node_modules/iterate/dist/starter-apps/notes/configured-worker.mjs\",\n files: {\n include: [\"package.json\"],\n repoPath: \"/repos/config\",\n type: \"repo\" as const,\n },\n },\n },\n type: \"stateful\" as const,\n};\n"],"mappings":";AAQA,MAAa,qBAAqB;AAClC,MAAa,gBAAgB;AAE7B,MAAa,iBAAiB;CAC5B,WAAW;CACX,kBAAkB;CAClB,MAAM;CACN,QAAQ,EACN,cAAc;EACZ,YAAY;EACZ,OAAO;GACL,SAAS,CAAC,cAAc;GACxB,UAAU;GACV,MAAM;EACR;CACF,EACF;CACA,MAAM;AACR"}
|
|
@@ -0,0 +1,427 @@
|
|
|
1
|
+
import { r as StreamProcessorDurableObject } from "../../sdk-DMB-IM11.mjs";
|
|
2
|
+
import { h as defineProcessorContract, s as StreamProcessor } from "../../stream-processor-keepalive-DAQTP6m3.mjs";
|
|
3
|
+
import { n as isIdempotencyConflict } from "../../idempotency-DleloJNt.mjs";
|
|
4
|
+
import "../../processors.mjs";
|
|
5
|
+
import { t as _usingCtx } from "../../usingCtx-inzbY1Qz.mjs";
|
|
6
|
+
import { notesRepoPath, notesWorkspacePath } from "./ref.mjs";
|
|
7
|
+
import { z } from "zod";
|
|
8
|
+
import * as YAML from "yaml";
|
|
9
|
+
//#region src/starter-apps/notes/frontmatter.ts
|
|
10
|
+
const FRONTMATTER_PATTERN = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/;
|
|
11
|
+
/** Tolerant parse: no frontmatter, or unparseable YAML, degrades to an empty
|
|
12
|
+
* frontmatter object with the full content as body — a note must never
|
|
13
|
+
* become unreadable because someone hand-edited its header badly. */
|
|
14
|
+
function parseNoteFile(content) {
|
|
15
|
+
const match = content.match(FRONTMATTER_PATTERN);
|
|
16
|
+
if (!match) return {
|
|
17
|
+
frontmatter: {},
|
|
18
|
+
body: content
|
|
19
|
+
};
|
|
20
|
+
try {
|
|
21
|
+
const parsed = YAML.parse(match[1]);
|
|
22
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) return {
|
|
23
|
+
frontmatter: {},
|
|
24
|
+
body: content
|
|
25
|
+
};
|
|
26
|
+
return {
|
|
27
|
+
frontmatter: parsed,
|
|
28
|
+
body: content.slice(match[0].length)
|
|
29
|
+
};
|
|
30
|
+
} catch {
|
|
31
|
+
return {
|
|
32
|
+
frontmatter: {},
|
|
33
|
+
body: content
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
function composeNoteFile(frontmatter, body) {
|
|
38
|
+
if (Object.keys(frontmatter).length === 0) return body;
|
|
39
|
+
return `---\n${YAML.stringify(frontmatter).trimEnd()}\n---\n${body}`;
|
|
40
|
+
}
|
|
41
|
+
/** The list/commit-message title: frontmatter title, else the body's first
|
|
42
|
+
* non-blank line — same fallback the phone shows while analysis is pending. */
|
|
43
|
+
function noteDisplayTitle(note) {
|
|
44
|
+
const title = note.frontmatter.title;
|
|
45
|
+
if (typeof title === "string" && title.trim() !== "") return title.trim();
|
|
46
|
+
return (note.body.split("\n").find((line) => line.trim() !== "") || "").trim().slice(0, 80);
|
|
47
|
+
}
|
|
48
|
+
//#endregion
|
|
49
|
+
//#region src/starter-apps/notes/processor.ts
|
|
50
|
+
/** Analyzes note text into a title + tags; the model id lands in `processedBy`. */
|
|
51
|
+
const NOTES_ANALYSIS_MODEL = "@cf/meta/llama-4-scout-17b-16e-instruct";
|
|
52
|
+
/** An obligation (including its settlement) is dead this long after the
|
|
53
|
+
* capture/reanalyze event that opened it — a processor revived later settles
|
|
54
|
+
* it as expired instead of titling a stale note. */
|
|
55
|
+
const NOTES_ANALYSIS_EXPIRY_MS = 1440 * 60 * 1e3;
|
|
56
|
+
/** Quiet window between a settled fact and the git commit that sweeps every
|
|
57
|
+
* dirty note in the mount — a burst of captures lands as one commit. */
|
|
58
|
+
const NOTES_COMMIT_DEBOUNCE_MS = 1e4;
|
|
59
|
+
const AnalysisResult = z.discriminatedUnion("status", [
|
|
60
|
+
z.object({
|
|
61
|
+
status: z.literal("succeeded"),
|
|
62
|
+
title: z.string(),
|
|
63
|
+
tags: z.array(z.string()),
|
|
64
|
+
processedBy: z.string()
|
|
65
|
+
}),
|
|
66
|
+
z.object({
|
|
67
|
+
status: z.literal("superseded"),
|
|
68
|
+
reason: z.string().meta({ description: "Why nothing was written (file empty/changed/gone/expired)." })
|
|
69
|
+
}),
|
|
70
|
+
z.object({
|
|
71
|
+
status: z.literal("failed"),
|
|
72
|
+
error: z.string()
|
|
73
|
+
})
|
|
74
|
+
]);
|
|
75
|
+
const NotesProcessorContract = defineProcessorContract({
|
|
76
|
+
slug: "notes",
|
|
77
|
+
version: "0.2.0",
|
|
78
|
+
description: "Hosts on the notes workspace stream; settles title/tags analysis into note-file frontmatter and drives the settlement-debounced git commit lane. Files are truth; this fold holds only open obligations.",
|
|
79
|
+
stateSchema: z.object({ pendingAnalyses: z.record(z.string(), z.object({
|
|
80
|
+
path: z.string(),
|
|
81
|
+
requestOffset: z.number(),
|
|
82
|
+
expiresAtMs: z.number()
|
|
83
|
+
})).default({}).meta({ description: "Open analysis obligations keyed `<path>:<requestOffset>` — opened by captured/updated/reanalyze-requested (each supersedes the note's older obligations), closed by analysis-settled." }) }),
|
|
84
|
+
events: {
|
|
85
|
+
"events.iterate.com/notes/captured": {
|
|
86
|
+
description: "A note file entered the notes repo: the writer (phone composer, or any client) wrote `<path>` through the notes workspace and appends this fact so the list updates live and an analysis obligation opens. The file is the truth; this event carries only the address.",
|
|
87
|
+
payloadSchema: z.object({ path: z.string() }),
|
|
88
|
+
examples: [{
|
|
89
|
+
description: "A capture from the phone composer.",
|
|
90
|
+
payload: { path: "/repos/notes/2026-08-12T15-01-20-841Z-x7ab.md" }
|
|
91
|
+
}]
|
|
92
|
+
},
|
|
93
|
+
"events.iterate.com/notes/updated": {
|
|
94
|
+
description: "The note file's body was edited (writer already rewrote the file). Supersedes the note's open obligations and opens a fresh one, so a slow stale attempt can neither settle nor write a title computed from the old text.",
|
|
95
|
+
payloadSchema: z.object({ path: z.string() }),
|
|
96
|
+
examples: [{
|
|
97
|
+
description: "A typo fixed after capture.",
|
|
98
|
+
payload: { path: "/repos/notes/2026-08-12T15-01-20-841Z-x7ab.md" }
|
|
99
|
+
}]
|
|
100
|
+
},
|
|
101
|
+
"events.iterate.com/notes/reanalyze-requested": {
|
|
102
|
+
description: "Re-run title/tags analysis for one note (supersedes + reopens, like updated).",
|
|
103
|
+
payloadSchema: z.object({ path: z.string() }),
|
|
104
|
+
examples: [{
|
|
105
|
+
description: "Re-analyze after a prompt improvement.",
|
|
106
|
+
payload: { path: "/repos/notes/2026-08-12T15-01-20-841Z-x7ab.md" }
|
|
107
|
+
}]
|
|
108
|
+
},
|
|
109
|
+
"events.iterate.com/notes/analysis-settled": {
|
|
110
|
+
description: "Terminal settlement of one analysis obligation. `succeeded` means the title/tags were ALSO written into the file's frontmatter (the durable artifact); `superseded` means the body was empty or changed, or the file vanished before write-back; `failed` carries the error. Exactly one per obligation, keyed on path + requestOffset.",
|
|
111
|
+
payloadSchema: z.object({
|
|
112
|
+
path: z.string(),
|
|
113
|
+
requestOffset: z.number(),
|
|
114
|
+
result: AnalysisResult
|
|
115
|
+
}),
|
|
116
|
+
examples: [{
|
|
117
|
+
description: "A successful analysis, frontmatter written.",
|
|
118
|
+
payload: {
|
|
119
|
+
path: "/repos/notes/2026-08-12T15-01-20-841Z-x7ab.md",
|
|
120
|
+
requestOffset: 4,
|
|
121
|
+
result: {
|
|
122
|
+
status: "succeeded",
|
|
123
|
+
title: "Standing desk height",
|
|
124
|
+
tags: ["reference"],
|
|
125
|
+
processedBy: NOTES_ANALYSIS_MODEL
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}]
|
|
129
|
+
},
|
|
130
|
+
"events.iterate.com/notes/deleted": {
|
|
131
|
+
description: "The note file was deleted through the workspace (writer already called deleteFile). Open obligations drop; the commit lane lands the git deletion.",
|
|
132
|
+
payloadSchema: z.object({ path: z.string() }),
|
|
133
|
+
examples: [{
|
|
134
|
+
description: "A mistyped capture removed.",
|
|
135
|
+
payload: { path: "/repos/notes/2026-08-12T15-01-20-841Z-x7ab.md" }
|
|
136
|
+
}]
|
|
137
|
+
}
|
|
138
|
+
},
|
|
139
|
+
consumes: [
|
|
140
|
+
"events.iterate.com/notes/captured",
|
|
141
|
+
"events.iterate.com/notes/updated",
|
|
142
|
+
"events.iterate.com/notes/reanalyze-requested",
|
|
143
|
+
"events.iterate.com/notes/analysis-settled",
|
|
144
|
+
"events.iterate.com/notes/deleted"
|
|
145
|
+
],
|
|
146
|
+
emits: ["events.iterate.com/notes/analysis-settled"]
|
|
147
|
+
});
|
|
148
|
+
var NotesProcessor = class extends StreamProcessor {
|
|
149
|
+
contract = NotesProcessorContract;
|
|
150
|
+
/** Runtime-only analysis attempts. The pendingAnalyses fold entries are the
|
|
151
|
+
* durable truth: after an eviction this set is empty, and the next
|
|
152
|
+
* caught-up pass safely restarts any still-open obligation. */
|
|
153
|
+
#liveAnalyses = /* @__PURE__ */ new Set();
|
|
154
|
+
/** Runtime-only commit-lane latch — at most one debounce+commit attempt per
|
|
155
|
+
* incarnation window. Recovery needs no evidence: every at-head pass
|
|
156
|
+
* re-derives dirtiness from the workspace itself (git status), so a
|
|
157
|
+
* dropped attempt is retried by the next batch or keepalive revival. */
|
|
158
|
+
#commitInFlight = false;
|
|
159
|
+
/** Opens (superseding) an analysis obligation for `path` at this event. */
|
|
160
|
+
#reopen(state, path, offset, createdAt) {
|
|
161
|
+
return { pendingAnalyses: {
|
|
162
|
+
...Object.fromEntries(Object.entries(state.pendingAnalyses).filter(([, pending]) => pending.path !== path)),
|
|
163
|
+
[`${path}:${offset}`]: {
|
|
164
|
+
path,
|
|
165
|
+
requestOffset: offset,
|
|
166
|
+
expiresAtMs: Date.parse(createdAt) + NOTES_ANALYSIS_EXPIRY_MS
|
|
167
|
+
}
|
|
168
|
+
} };
|
|
169
|
+
}
|
|
170
|
+
reduce({ event, state }) {
|
|
171
|
+
switch (event.type) {
|
|
172
|
+
case "events.iterate.com/notes/captured":
|
|
173
|
+
case "events.iterate.com/notes/updated":
|
|
174
|
+
case "events.iterate.com/notes/reanalyze-requested": return this.#reopen(state, event.payload.path, event.offset, event.createdAt);
|
|
175
|
+
case "events.iterate.com/notes/analysis-settled": {
|
|
176
|
+
const obligationKey = `${event.payload.path}:${event.payload.requestOffset}`;
|
|
177
|
+
if (state.pendingAnalyses[obligationKey] === void 0) return state;
|
|
178
|
+
const { [obligationKey]: _settled, ...pendingAnalyses } = state.pendingAnalyses;
|
|
179
|
+
return { pendingAnalyses };
|
|
180
|
+
}
|
|
181
|
+
case "events.iterate.com/notes/deleted": return { pendingAnalyses: Object.fromEntries(Object.entries(state.pendingAnalyses).filter(([, pending]) => pending.path !== event.payload.path)) };
|
|
182
|
+
default: return state;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
processEvent(args) {
|
|
186
|
+
const { append, blockProcessorWhile, delivery, runInBackground, state } = args;
|
|
187
|
+
if (!delivery.caughtUp) return;
|
|
188
|
+
for (const [obligationKey, pending] of Object.entries(state.pendingAnalyses)) {
|
|
189
|
+
if (this.#liveAnalyses.has(obligationKey)) continue;
|
|
190
|
+
const settlementKey = this.idempotencyKey(`analysis-settled@${obligationKey}`);
|
|
191
|
+
if (this.deps.now() > pending.expiresAtMs) {
|
|
192
|
+
blockProcessorWhile(() => this.#appendUnlessLostSettlementRace(append, {
|
|
193
|
+
type: "events.iterate.com/notes/analysis-settled",
|
|
194
|
+
idempotencyKey: settlementKey,
|
|
195
|
+
payload: {
|
|
196
|
+
path: pending.path,
|
|
197
|
+
requestOffset: pending.requestOffset,
|
|
198
|
+
result: {
|
|
199
|
+
status: "failed",
|
|
200
|
+
error: "Analysis obligation expired before completion."
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}));
|
|
204
|
+
continue;
|
|
205
|
+
}
|
|
206
|
+
this.#liveAnalyses.add(obligationKey);
|
|
207
|
+
runInBackground(async () => {
|
|
208
|
+
let result;
|
|
209
|
+
try {
|
|
210
|
+
result = await this.#attemptAnalysis(pending.path);
|
|
211
|
+
} catch (error) {
|
|
212
|
+
result = {
|
|
213
|
+
status: "failed",
|
|
214
|
+
error: (error instanceof Error ? error.message : String(error)).slice(0, 8e3) || "Unknown analysis failure."
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
try {
|
|
218
|
+
await this.#appendUnlessLostSettlementRace(append, {
|
|
219
|
+
type: "events.iterate.com/notes/analysis-settled",
|
|
220
|
+
idempotencyKey: settlementKey,
|
|
221
|
+
payload: {
|
|
222
|
+
path: pending.path,
|
|
223
|
+
requestOffset: pending.requestOffset,
|
|
224
|
+
result
|
|
225
|
+
}
|
|
226
|
+
});
|
|
227
|
+
} finally {
|
|
228
|
+
this.#liveAnalyses.delete(obligationKey);
|
|
229
|
+
}
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
if (!this.#commitInFlight) {
|
|
233
|
+
this.#commitInFlight = true;
|
|
234
|
+
runInBackground(async () => {
|
|
235
|
+
try {
|
|
236
|
+
await this.deps.sleep(NOTES_COMMIT_DEBOUNCE_MS);
|
|
237
|
+
const dirty = await this.deps.workspace.dirtyNotePaths();
|
|
238
|
+
if (dirty.length === 0) return;
|
|
239
|
+
await this.deps.workspace.commit({
|
|
240
|
+
message: await this.#commitMessage(dirty),
|
|
241
|
+
scope: notesRepoPath
|
|
242
|
+
});
|
|
243
|
+
} finally {
|
|
244
|
+
this.#commitInFlight = false;
|
|
245
|
+
}
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
/** Read → analyze → re-read guard → frontmatter write-back. The write
|
|
250
|
+
* composes from the RE-READ file, so foreign frontmatter keys and any
|
|
251
|
+
* concurrent body edit are respected (body changed ⇒ superseded, nothing
|
|
252
|
+
* written). Idempotent-by-overwrite on redelivery. */
|
|
253
|
+
async #attemptAnalysis(path) {
|
|
254
|
+
const content = await this.deps.workspace.readFile(path);
|
|
255
|
+
if (content === null) return {
|
|
256
|
+
status: "superseded",
|
|
257
|
+
reason: "note file no longer exists"
|
|
258
|
+
};
|
|
259
|
+
const note = parseNoteFile(content);
|
|
260
|
+
if (!note.body.trim()) return {
|
|
261
|
+
status: "superseded",
|
|
262
|
+
reason: "note has no text to analyze"
|
|
263
|
+
};
|
|
264
|
+
const analysis = await this.deps.analyze({ text: note.body });
|
|
265
|
+
const current = await this.deps.workspace.readFile(path);
|
|
266
|
+
if (current === null) return {
|
|
267
|
+
status: "superseded",
|
|
268
|
+
reason: "note file deleted during analysis"
|
|
269
|
+
};
|
|
270
|
+
const currentNote = parseNoteFile(current);
|
|
271
|
+
if (currentNote.body !== note.body) return {
|
|
272
|
+
status: "superseded",
|
|
273
|
+
reason: "note body changed during analysis"
|
|
274
|
+
};
|
|
275
|
+
const edited = await this.deps.workspace.edit({
|
|
276
|
+
path,
|
|
277
|
+
oldString: current,
|
|
278
|
+
newString: composeNoteFile({
|
|
279
|
+
...currentNote.frontmatter,
|
|
280
|
+
title: analysis.title,
|
|
281
|
+
tags: analysis.tags
|
|
282
|
+
}, currentNote.body)
|
|
283
|
+
});
|
|
284
|
+
if (edited.status === "not-applied") return {
|
|
285
|
+
status: "superseded",
|
|
286
|
+
reason: edited.reason
|
|
287
|
+
};
|
|
288
|
+
return {
|
|
289
|
+
status: "succeeded",
|
|
290
|
+
...analysis
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
/** "Standing desk height (+2 more)" — the first dirty note's display title
|
|
294
|
+
* (or its filename stem when the file is a deletion), sized to a git
|
|
295
|
+
* subject line. */
|
|
296
|
+
async #commitMessage(dirtyPaths) {
|
|
297
|
+
const [first] = dirtyPaths;
|
|
298
|
+
const content = first === void 0 ? null : await this.deps.workspace.readFile(first);
|
|
299
|
+
const stem = (first || "").split("/").at(-1)?.replace(/\.md$/, "") || "notes";
|
|
300
|
+
const label = content === null ? `remove ${stem}` : noteDisplayTitle(parseNoteFile(content)) || stem;
|
|
301
|
+
const rest = dirtyPaths.length - 1;
|
|
302
|
+
return `notes: ${label}${rest > 0 ? ` (+${rest} more)` : ""}`.slice(0, 100);
|
|
303
|
+
}
|
|
304
|
+
/** Overlapping attempts (a zombie incarnation, an expiry racing a late
|
|
305
|
+
* success) share one terminal key; the first committed settlement is the
|
|
306
|
+
* authority, so a same-key/different-body rejection means "already
|
|
307
|
+
* settled", not "wedge the frame". */
|
|
308
|
+
async #appendUnlessLostSettlementRace(append, event) {
|
|
309
|
+
try {
|
|
310
|
+
await append(event);
|
|
311
|
+
} catch (error) {
|
|
312
|
+
if (!isIdempotencyConflict(error)) throw error;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
};
|
|
316
|
+
//#endregion
|
|
317
|
+
//#region src/starter-apps/notes/analysis.ts
|
|
318
|
+
async function analyzeNoteText(ai, input) {
|
|
319
|
+
const answer = await ai.run(NOTES_ANALYSIS_MODEL, {
|
|
320
|
+
messages: [{
|
|
321
|
+
role: "user",
|
|
322
|
+
content: `${notesAnalysisPrompt()}\n\nNote:\n${input.text}`
|
|
323
|
+
}],
|
|
324
|
+
max_tokens: 256
|
|
325
|
+
});
|
|
326
|
+
const text = typeof answer?.response === "string" ? answer.response : typeof answer?.choices?.[0]?.message?.content === "string" ? answer.choices[0].message.content : "";
|
|
327
|
+
let title = "";
|
|
328
|
+
let tags = ["untagged"];
|
|
329
|
+
const match = text.match(/\{[\s\S]*\}/);
|
|
330
|
+
if (match) try {
|
|
331
|
+
const parsed = JSON.parse(match[0]);
|
|
332
|
+
if (typeof parsed.title === "string") title = parsed.title.trim().slice(0, 120);
|
|
333
|
+
if (Array.isArray(parsed.tags)) tags = [...new Set(parsed.tags.filter((tag) => typeof tag === "string").map((tag) => tag.toLowerCase().trim().replace(/[^a-z0-9-]+/g, "-").replace(/^-+|-+$/g, "")).filter((tag) => tag.length > 0 && tag.length <= 30))].slice(0, 4);
|
|
334
|
+
} catch {}
|
|
335
|
+
return {
|
|
336
|
+
title,
|
|
337
|
+
tags,
|
|
338
|
+
processedBy: NOTES_ANALYSIS_MODEL
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
function notesAnalysisPrompt() {
|
|
342
|
+
return [
|
|
343
|
+
"Reply with ONLY a JSON object: {\"title\": string, \"tags\": string[]}.",
|
|
344
|
+
"title: ONE line saying what the note is about, specific not generic — 'Standing desk: 76cm' never 'A note' or 'User's thoughts'. Reuse the note's own words where possible.",
|
|
345
|
+
"tags: up to 4 kebab-case topical tags (e.g. idea, todo, reference, shopping). Fewer is better; an empty array is a fine answer."
|
|
346
|
+
].join("\n");
|
|
347
|
+
}
|
|
348
|
+
//#endregion
|
|
349
|
+
//#region src/starter-apps/notes/worker.ts
|
|
350
|
+
var NotesApp = class extends StreamProcessorDurableObject {
|
|
351
|
+
streamPath = notesWorkspacePath;
|
|
352
|
+
/** The processor owes background work (analysis attempts, the debounced
|
|
353
|
+
* commit) — an eviction must revive and settle, not drop it. */
|
|
354
|
+
recovery = true;
|
|
355
|
+
createProcessor(deps) {
|
|
356
|
+
return new NotesProcessor({
|
|
357
|
+
...deps,
|
|
358
|
+
now: () => Date.now(),
|
|
359
|
+
sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
|
|
360
|
+
analyze: async (input) => {
|
|
361
|
+
try {
|
|
362
|
+
var _usingCtx$1 = _usingCtx();
|
|
363
|
+
return await analyzeNoteText(_usingCtx$1.u(await this.env.ITX.get()).ai, input);
|
|
364
|
+
} catch (_) {
|
|
365
|
+
_usingCtx$1.e = _;
|
|
366
|
+
} finally {
|
|
367
|
+
_usingCtx$1.d();
|
|
368
|
+
}
|
|
369
|
+
},
|
|
370
|
+
workspace: {
|
|
371
|
+
readFile: async (path) => {
|
|
372
|
+
try {
|
|
373
|
+
var _usingCtx3 = _usingCtx();
|
|
374
|
+
return await _usingCtx3.u(await this.env.ITX.get()).workspaces.get(notesWorkspacePath).readFile(path);
|
|
375
|
+
} catch (_) {
|
|
376
|
+
_usingCtx3.e = _;
|
|
377
|
+
} finally {
|
|
378
|
+
_usingCtx3.d();
|
|
379
|
+
}
|
|
380
|
+
},
|
|
381
|
+
edit: async (input) => {
|
|
382
|
+
try {
|
|
383
|
+
var _usingCtx4 = _usingCtx();
|
|
384
|
+
return await _usingCtx4.u(await this.env.ITX.get()).workspaces.get(notesWorkspacePath).edit(input);
|
|
385
|
+
} catch (_) {
|
|
386
|
+
_usingCtx4.e = _;
|
|
387
|
+
} finally {
|
|
388
|
+
_usingCtx4.d();
|
|
389
|
+
}
|
|
390
|
+
},
|
|
391
|
+
dirtyNotePaths: async () => {
|
|
392
|
+
try {
|
|
393
|
+
var _usingCtx5 = _usingCtx();
|
|
394
|
+
return ((await _usingCtx5.u(await this.env.ITX.get()).workspaces.get("/workspaces/notes").git.status()).mounts.find((candidate) => candidate.path === "/repos/notes")?.changes || []).map((change) => change.path);
|
|
395
|
+
} catch (_) {
|
|
396
|
+
_usingCtx5.e = _;
|
|
397
|
+
} finally {
|
|
398
|
+
_usingCtx5.d();
|
|
399
|
+
}
|
|
400
|
+
},
|
|
401
|
+
commit: async (input) => {
|
|
402
|
+
try {
|
|
403
|
+
var _usingCtx6 = _usingCtx();
|
|
404
|
+
await _usingCtx6.u(await this.env.ITX.get()).workspaces.get(notesWorkspacePath).git.commit(input);
|
|
405
|
+
} catch (_) {
|
|
406
|
+
_usingCtx6.e = _;
|
|
407
|
+
} finally {
|
|
408
|
+
_usingCtx6.d();
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
/** Project-worker event delivery calls this after a durable event commits
|
|
415
|
+
* on the notes workspace stream. Catch-up owns validation, ordering,
|
|
416
|
+
* checkpointing, and dedupe. */
|
|
417
|
+
async syncEvent(event) {
|
|
418
|
+
if (event.path !== "/workspaces/notes") return;
|
|
419
|
+
const registry = await this.registry();
|
|
420
|
+
await registry.catchUp("notes");
|
|
421
|
+
registry.refreshLive();
|
|
422
|
+
}
|
|
423
|
+
};
|
|
424
|
+
//#endregion
|
|
425
|
+
export { NotesApp };
|
|
426
|
+
|
|
427
|
+
//# sourceMappingURL=worker.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"worker.mjs","names":["#liveAnalyses","#reopen","#appendUnlessLostSettlementRace","#attemptAnalysis","#commitInFlight","#commitMessage"],"sources":["../../../src/starter-apps/notes/frontmatter.ts","../../../src/starter-apps/notes/processor.ts","../../../src/starter-apps/notes/analysis.ts","../../../src/starter-apps/notes/worker.ts"],"sourcesContent":["// A note IS a markdown file with YAML frontmatter (convergence decision D3/D4)\n// — this module is the one place that composes and parses that shape. Unknown\n// frontmatter keys are preserved on recompose: agents and humans may add\n// their own fields, and the analysis write-back must never eat them.\n//\n// Mirrored by hand in apps/mobile/src/lib/notes.ts (the two runtimes can't\n// share a module yet — same arrangement as media's filter logic).\nimport * as YAML from \"yaml\";\n\ntype NoteFile = {\n /** Parsed frontmatter, all keys preserved. Empty object when absent/broken. */\n frontmatter: Record<string, unknown>;\n /** Everything after the frontmatter block — the note text. */\n body: string;\n};\n\nconst FRONTMATTER_PATTERN = /^---\\r?\\n([\\s\\S]*?)\\r?\\n---\\r?\\n?/;\n\n/** Tolerant parse: no frontmatter, or unparseable YAML, degrades to an empty\n * frontmatter object with the full content as body — a note must never\n * become unreadable because someone hand-edited its header badly. */\nexport function parseNoteFile(content: string): NoteFile {\n const match = content.match(FRONTMATTER_PATTERN);\n if (!match) return { frontmatter: {}, body: content };\n try {\n const parsed = YAML.parse(match[1]!);\n if (parsed === null || typeof parsed !== \"object\" || Array.isArray(parsed)) {\n return { frontmatter: {}, body: content };\n }\n return { frontmatter: parsed as Record<string, unknown>, body: content.slice(match[0].length) };\n } catch {\n return { frontmatter: {}, body: content };\n }\n}\n\nexport function composeNoteFile(frontmatter: Record<string, unknown>, body: string): string {\n if (Object.keys(frontmatter).length === 0) return body;\n return `---\\n${YAML.stringify(frontmatter).trimEnd()}\\n---\\n${body}`;\n}\n\n/** The list/commit-message title: frontmatter title, else the body's first\n * non-blank line — same fallback the phone shows while analysis is pending. */\nexport function noteDisplayTitle(note: NoteFile): string {\n const title = note.frontmatter.title;\n if (typeof title === \"string\" && title.trim() !== \"\") return title.trim();\n return (note.body.split(\"\\n\").find((line) => line.trim() !== \"\") || \"\").trim().slice(0, 80);\n}\n","// The notes stream processor, convergence edition (tasks/mobile-notes.md\n// grill session 2): a note IS a markdown file with frontmatter in the notes\n// repo, and this processor owns the two side effects that make it more than\n// a file — title/tags ANALYSIS written back into the file's frontmatter, and\n// the git COMMIT lane that lands settled notes on the repo's main. It hosts\n// on the notes workspace's own stream (/workspaces/notes), where writers\n// append notes/* facts after their file writes (writeFile itself emits\n// nothing durable). Invariant: nothing lives only in the stream — the fold\n// holds obligations, never note content; files are truth.\nimport { z } from \"zod\";\nimport type { EditWorkspaceFileResult } from \"../../itx-api.generated.ts\";\nimport {\n defineProcessorContract,\n isIdempotencyConflict,\n StreamProcessor,\n} from \"../../processors/index.ts\";\nimport type { ProcessEventArgs, ProcessorState, ReduceArgs } from \"../../processors/index.ts\";\nimport { composeNoteFile, noteDisplayTitle, parseNoteFile } from \"./frontmatter.ts\";\nimport { notesRepoPath } from \"./ref.ts\";\n\n/** Analyzes note text into a title + tags; the model id lands in `processedBy`. */\nexport const NOTES_ANALYSIS_MODEL = \"@cf/meta/llama-4-scout-17b-16e-instruct\";\n\n/** An obligation (including its settlement) is dead this long after the\n * capture/reanalyze event that opened it — a processor revived later settles\n * it as expired instead of titling a stale note. */\nexport const NOTES_ANALYSIS_EXPIRY_MS = 24 * 60 * 60 * 1000;\n\n/** Quiet window between a settled fact and the git commit that sweeps every\n * dirty note in the mount — a burst of captures lands as one commit. */\nexport const NOTES_COMMIT_DEBOUNCE_MS = 10_000;\n\nconst AnalysisResult = z.discriminatedUnion(\"status\", [\n z.object({\n status: z.literal(\"succeeded\"),\n title: z.string(),\n tags: z.array(z.string()),\n processedBy: z.string(),\n }),\n z.object({\n status: z.literal(\"superseded\"),\n reason: z\n .string()\n .meta({ description: \"Why nothing was written (file empty/changed/gone/expired).\" }),\n }),\n z.object({ status: z.literal(\"failed\"), error: z.string() }),\n]);\n\nexport const NotesProcessorContract = defineProcessorContract({\n slug: \"notes\",\n version: \"0.2.0\",\n description:\n \"Hosts on the notes workspace stream; settles title/tags analysis into note-file frontmatter \" +\n \"and drives the settlement-debounced git commit lane. Files are truth; this fold holds only \" +\n \"open obligations.\",\n stateSchema: z.object({\n pendingAnalyses: z\n .record(\n z.string(),\n z.object({\n path: z.string(),\n requestOffset: z.number(),\n expiresAtMs: z.number(),\n }),\n )\n .default({})\n .meta({\n description:\n \"Open analysis obligations keyed `<path>:<requestOffset>` — opened by captured/updated/\" +\n \"reanalyze-requested (each supersedes the note's older obligations), closed by analysis-settled.\",\n }),\n }),\n events: {\n \"events.iterate.com/notes/captured\": {\n description:\n \"A note file entered the notes repo: the writer (phone composer, or any client) wrote \" +\n \"`<path>` through the notes workspace and appends this fact so the list updates live and \" +\n \"an analysis obligation opens. The file is the truth; this event carries only the address.\",\n payloadSchema: z.object({ path: z.string() }),\n examples: [\n {\n description: \"A capture from the phone composer.\",\n payload: { path: \"/repos/notes/2026-08-12T15-01-20-841Z-x7ab.md\" },\n },\n ],\n },\n \"events.iterate.com/notes/updated\": {\n description:\n \"The note file's body was edited (writer already rewrote the file). Supersedes the note's \" +\n \"open obligations and opens a fresh one, so a slow stale attempt can neither settle nor \" +\n \"write a title computed from the old text.\",\n payloadSchema: z.object({ path: z.string() }),\n examples: [\n {\n description: \"A typo fixed after capture.\",\n payload: { path: \"/repos/notes/2026-08-12T15-01-20-841Z-x7ab.md\" },\n },\n ],\n },\n \"events.iterate.com/notes/reanalyze-requested\": {\n description: \"Re-run title/tags analysis for one note (supersedes + reopens, like updated).\",\n payloadSchema: z.object({ path: z.string() }),\n examples: [\n {\n description: \"Re-analyze after a prompt improvement.\",\n payload: { path: \"/repos/notes/2026-08-12T15-01-20-841Z-x7ab.md\" },\n },\n ],\n },\n \"events.iterate.com/notes/analysis-settled\": {\n description:\n \"Terminal settlement of one analysis obligation. `succeeded` means the title/tags were \" +\n \"ALSO written into the file's frontmatter (the durable artifact); `superseded` means the \" +\n \"body was empty or changed, or the file vanished before write-back; `failed` carries the error. Exactly \" +\n \"one per obligation, keyed on path + requestOffset.\",\n payloadSchema: z.object({\n path: z.string(),\n requestOffset: z.number(),\n result: AnalysisResult,\n }),\n examples: [\n {\n description: \"A successful analysis, frontmatter written.\",\n payload: {\n path: \"/repos/notes/2026-08-12T15-01-20-841Z-x7ab.md\",\n requestOffset: 4,\n result: {\n status: \"succeeded\",\n title: \"Standing desk height\",\n tags: [\"reference\"],\n processedBy: NOTES_ANALYSIS_MODEL,\n },\n },\n },\n ],\n },\n \"events.iterate.com/notes/deleted\": {\n description:\n \"The note file was deleted through the workspace (writer already called deleteFile). \" +\n \"Open obligations drop; the commit lane lands the git deletion.\",\n payloadSchema: z.object({ path: z.string() }),\n examples: [\n {\n description: \"A mistyped capture removed.\",\n payload: { path: \"/repos/notes/2026-08-12T15-01-20-841Z-x7ab.md\" },\n },\n ],\n },\n },\n consumes: [\n \"events.iterate.com/notes/captured\",\n \"events.iterate.com/notes/updated\",\n \"events.iterate.com/notes/reanalyze-requested\",\n \"events.iterate.com/notes/analysis-settled\",\n \"events.iterate.com/notes/deleted\",\n ],\n emits: [\"events.iterate.com/notes/analysis-settled\"],\n});\nexport type NotesProcessorContract = typeof NotesProcessorContract;\n\nexport type NotesState = ProcessorState<NotesProcessorContract>;\n\nexport type NotesAnalysis = { title: string; tags: string[]; processedBy: string };\n\n/** The workspace slice the processor needs — injected so the harness fakes it\n * with an in-memory file map and the worker wires it over itx per call. */\nexport type NotesWorkspace = {\n readFile(path: string): Promise<string | null>;\n edit(input: {\n path: string;\n oldString: string;\n newString: string;\n }): Promise<EditWorkspaceFileResult>;\n /** Paths dirty in the notes mount (relative to git truth). */\n dirtyNotePaths(): Promise<string[]>;\n commit(input: { message: string; scope: string }): Promise<void>;\n};\n\ntype NotesProcessorDeps = {\n analyze: (input: { text: string }) => Promise<NotesAnalysis>;\n workspace: NotesWorkspace;\n now: () => number;\n sleep: (ms: number) => Promise<void>;\n};\n\nexport class NotesProcessor extends StreamProcessor<NotesProcessorContract, NotesProcessorDeps> {\n readonly contract = NotesProcessorContract;\n\n /** Runtime-only analysis attempts. The pendingAnalyses fold entries are the\n * durable truth: after an eviction this set is empty, and the next\n * caught-up pass safely restarts any still-open obligation. */\n readonly #liveAnalyses = new Set<string>();\n\n /** Runtime-only commit-lane latch — at most one debounce+commit attempt per\n * incarnation window. Recovery needs no evidence: every at-head pass\n * re-derives dirtiness from the workspace itself (git status), so a\n * dropped attempt is retried by the next batch or keepalive revival. */\n #commitInFlight = false;\n\n /** Opens (superseding) an analysis obligation for `path` at this event. */\n #reopen(state: NotesState, path: string, offset: number, createdAt: string): NotesState {\n return {\n pendingAnalyses: {\n // One open obligation per note: older ones are superseded so a slow\n // stale attempt's settlement folds to a no-op (unknown-obligation\n // guard) instead of racing this one for the frontmatter.\n ...Object.fromEntries(\n Object.entries(state.pendingAnalyses).filter(([, pending]) => pending.path !== path),\n ),\n [`${path}:${offset}`]: {\n path,\n requestOffset: offset,\n // Anchored to the event's own time, not the delivery clock — a\n // replayed frame must reduce to the same expiry.\n expiresAtMs: Date.parse(createdAt) + NOTES_ANALYSIS_EXPIRY_MS,\n },\n },\n };\n }\n\n protected override reduce({ event, state }: ReduceArgs<NotesProcessorContract>): NotesState {\n switch (event.type) {\n case \"events.iterate.com/notes/captured\":\n case \"events.iterate.com/notes/updated\":\n case \"events.iterate.com/notes/reanalyze-requested\":\n return this.#reopen(state, event.payload.path, event.offset, event.createdAt);\n case \"events.iterate.com/notes/analysis-settled\": {\n const obligationKey = `${event.payload.path}:${event.payload.requestOffset}`;\n // A settlement for an unknown obligation (already settled, or\n // superseded by a later capture/edit/delete) folds to a no-op.\n if (state.pendingAnalyses[obligationKey] === undefined) return state;\n const { [obligationKey]: _settled, ...pendingAnalyses } = state.pendingAnalyses;\n return { pendingAnalyses };\n }\n case \"events.iterate.com/notes/deleted\":\n return {\n pendingAnalyses: Object.fromEntries(\n Object.entries(state.pendingAnalyses).filter(\n ([, pending]) => pending.path !== event.payload.path,\n ),\n ),\n };\n default:\n return state;\n }\n }\n\n protected override processEvent(args: ProcessEventArgs<NotesProcessorContract>): undefined {\n const { append, blockProcessorWhile, delivery, runInBackground, state } = args;\n // Behind head, a settlement may already sit in an unseen page — starting\n // an analysis from partial state would double-run it.\n if (!delivery.caughtUp) return;\n\n for (const [obligationKey, pending] of Object.entries(state.pendingAnalyses)) {\n if (this.#liveAnalyses.has(obligationKey)) continue;\n const settlementKey = this.idempotencyKey(`analysis-settled@${obligationKey}`);\n\n if (this.deps.now() > pending.expiresAtMs) {\n // Short must-land append: without it a revival long after the capture\n // would re-attempt a stale analysis forever. Held so redelivery\n // retries it if this frame drops.\n blockProcessorWhile(() =>\n this.#appendUnlessLostSettlementRace(append, {\n type: \"events.iterate.com/notes/analysis-settled\",\n idempotencyKey: settlementKey,\n payload: {\n path: pending.path,\n requestOffset: pending.requestOffset,\n result: { status: \"failed\", error: \"Analysis obligation expired before completion.\" },\n },\n }),\n );\n continue;\n }\n\n // Registered synchronously before any await so this same pass never\n // classifies its own attempt as undriven. A dropped attempt is\n // recovered from the still-open pendingAnalyses entry on the next\n // caught-up/revival pass (recovery = true on the hosting DO).\n this.#liveAnalyses.add(obligationKey);\n runInBackground(async () => {\n let result: z.infer<typeof AnalysisResult>;\n try {\n result = await this.#attemptAnalysis(pending.path);\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n result = {\n status: \"failed\",\n error: message.slice(0, 8_000) || \"Unknown analysis failure.\",\n };\n }\n try {\n await this.#appendUnlessLostSettlementRace(append, {\n type: \"events.iterate.com/notes/analysis-settled\",\n idempotencyKey: settlementKey,\n payload: { path: pending.path, requestOffset: pending.requestOffset, result },\n });\n } finally {\n this.#liveAnalyses.delete(obligationKey);\n }\n });\n }\n\n // The commit lane: after the debounce quiet window, sweep every dirty\n // note in the mount into one commit. A droppable attempt — recovery is\n // this very block on the next at-head pass (or keepalive revival):\n // dirtiness is re-derived from the workspace itself each time, and a\n // clean tree makes the attempt a no-op, so replays never re-commit.\n if (!this.#commitInFlight) {\n this.#commitInFlight = true;\n runInBackground(async () => {\n try {\n await this.deps.sleep(NOTES_COMMIT_DEBOUNCE_MS);\n const dirty = await this.deps.workspace.dirtyNotePaths();\n if (dirty.length === 0) return;\n await this.deps.workspace.commit({\n message: await this.#commitMessage(dirty),\n scope: notesRepoPath,\n });\n } finally {\n this.#commitInFlight = false;\n }\n });\n }\n }\n\n /** Read → analyze → re-read guard → frontmatter write-back. The write\n * composes from the RE-READ file, so foreign frontmatter keys and any\n * concurrent body edit are respected (body changed ⇒ superseded, nothing\n * written). Idempotent-by-overwrite on redelivery. */\n async #attemptAnalysis(path: string): Promise<z.infer<typeof AnalysisResult>> {\n const content = await this.deps.workspace.readFile(path);\n if (content === null) {\n return { status: \"superseded\", reason: \"note file no longer exists\" };\n }\n const note = parseNoteFile(content);\n if (!note.body.trim()) {\n return { status: \"superseded\", reason: \"note has no text to analyze\" };\n }\n const analysis = await this.deps.analyze({ text: note.body });\n\n const current = await this.deps.workspace.readFile(path);\n if (current === null) {\n return { status: \"superseded\", reason: \"note file deleted during analysis\" };\n }\n const currentNote = parseNoteFile(current);\n if (currentNote.body !== note.body) {\n return { status: \"superseded\", reason: \"note body changed during analysis\" };\n }\n // The final read and this write cross an RPC boundary. An unconditional\n // write can still replace a user's edit committed after the guard read.\n // Workspace.edit checks the old contents inside its serialized write.\n const edited = await this.deps.workspace.edit({\n path,\n oldString: current,\n newString: composeNoteFile(\n { ...currentNote.frontmatter, title: analysis.title, tags: analysis.tags },\n currentNote.body,\n ),\n });\n if (edited.status === \"not-applied\") {\n return { status: \"superseded\", reason: edited.reason };\n }\n return { status: \"succeeded\", ...analysis };\n }\n\n /** \"Standing desk height (+2 more)\" — the first dirty note's display title\n * (or its filename stem when the file is a deletion), sized to a git\n * subject line. */\n async #commitMessage(dirtyPaths: string[]): Promise<string> {\n const [first] = dirtyPaths;\n const content = first === undefined ? null : await this.deps.workspace.readFile(first);\n const stem = (first || \"\").split(\"/\").at(-1)?.replace(/\\.md$/, \"\") || \"notes\";\n const label =\n content === null ? `remove ${stem}` : noteDisplayTitle(parseNoteFile(content)) || stem;\n const rest = dirtyPaths.length - 1;\n return `notes: ${label}${rest > 0 ? ` (+${rest} more)` : \"\"}`.slice(0, 100);\n }\n\n /** Overlapping attempts (a zombie incarnation, an expiry racing a late\n * success) share one terminal key; the first committed settlement is the\n * authority, so a same-key/different-body rejection means \"already\n * settled\", not \"wedge the frame\". */\n async #appendUnlessLostSettlementRace(\n append: ProcessEventArgs<NotesProcessorContract>[\"append\"],\n event: Parameters<ProcessEventArgs<NotesProcessorContract>[\"append\"]>[0],\n ): Promise<void> {\n try {\n await append(event);\n } catch (error) {\n if (!isIdempotencyConflict(error)) throw error;\n }\n }\n}\n","// The analysis half the NotesApp worker injects as the processor's `analyze`\n// dep: one Workers AI text call over the note's raw text returning\n// {title, tags}. Parse defensively — an unparseable answer degrades to an\n// empty title and [\"untagged\"] so failures stay visible in the list rather\n// than throwing the obligation into a retry loop.\nimport { NOTES_ANALYSIS_MODEL, type NotesAnalysis } from \"./processor.ts\";\n\nexport async function analyzeNoteText(\n ai: { run: (model: string, body: Record<string, unknown>) => Promise<unknown> },\n input: { text: string },\n): Promise<NotesAnalysis> {\n const answer: any = await ai.run(NOTES_ANALYSIS_MODEL, {\n messages: [{ role: \"user\", content: `${notesAnalysisPrompt()}\\n\\nNote:\\n${input.text}` }],\n max_tokens: 256,\n });\n const text =\n typeof answer?.response === \"string\"\n ? answer.response\n : typeof answer?.choices?.[0]?.message?.content === \"string\"\n ? answer.choices[0].message.content\n : \"\";\n let title = \"\";\n let tags = [\"untagged\"];\n const match = text.match(/\\{[\\s\\S]*\\}/);\n if (match) {\n try {\n const parsed = JSON.parse(match[0]);\n if (typeof parsed.title === \"string\") title = parsed.title.trim().slice(0, 120);\n if (Array.isArray(parsed.tags)) {\n tags = [\n ...new Set<string>(\n parsed.tags\n .filter((tag: unknown): tag is string => typeof tag === \"string\")\n .map((tag: string) =>\n tag\n .toLowerCase()\n .trim()\n .replace(/[^a-z0-9-]+/g, \"-\")\n .replace(/^-+|-+$/g, \"\"),\n )\n .filter((tag: string) => tag.length > 0 && tag.length <= 30),\n ),\n ].slice(0, 4);\n }\n } catch {}\n }\n return { title, tags, processedBy: NOTES_ANALYSIS_MODEL };\n}\n\nfunction notesAnalysisPrompt(): string {\n return [\n 'Reply with ONLY a JSON object: {\"title\": string, \"tags\": string[]}.',\n \"title: ONE line saying what the note is about, specific not generic — 'Standing desk: 76cm' \" +\n \"never 'A note' or 'User's thoughts'. Reuse the note's own words where possible.\",\n \"tags: up to 4 kebab-case topical tags (e.g. idea, todo, reference, shopping). \" +\n \"Fewer is better; an empty array is a fine answer.\",\n ].join(\"\\n\");\n}\n","// The NotesApp Durable Object: hosts the notes stream processor on the notes\n// workspace's stream — analysis obligations (frontmatter write-back) and the\n// settlement-debounced git commit lane, hence recovery. No query surface:\n// notes are ordinary workspace documents, found via glob/readFiles like any\n// other file (convergence decision D6).\nimport {\n StreamProcessorDurableObject,\n type ProcessorHostDeps,\n type StreamEvent,\n} from \"../../sdk.ts\";\nimport { notesRepoPath, notesWorkspacePath } from \"./app-ref.ts\";\nimport { analyzeNoteText } from \"./analysis.ts\";\nimport { NotesProcessor, type NotesState } from \"./processor.ts\";\n\nexport class NotesApp extends StreamProcessorDurableObject<NotesState> {\n protected readonly streamPath = notesWorkspacePath;\n /** The processor owes background work (analysis attempts, the debounced\n * commit) — an eviction must revive and settle, not drop it. */\n protected readonly recovery = true;\n\n protected createProcessor(deps: ProcessorHostDeps) {\n return new NotesProcessor({\n ...deps,\n now: () => Date.now(),\n sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms)),\n analyze: async (input) => {\n using project = await this.env.ITX.get();\n return await analyzeNoteText(project.ai, input);\n },\n // Each verb opens its own itx session — a stub must not outlive its\n // RPC turn (the facet alarm-proxy pattern).\n workspace: {\n readFile: async (path) => {\n using project = await this.env.ITX.get();\n return await project.workspaces.get(notesWorkspacePath).readFile(path);\n },\n edit: async (input) => {\n using project = await this.env.ITX.get();\n return await project.workspaces.get(notesWorkspacePath).edit(input);\n },\n dirtyNotePaths: async () => {\n using project = await this.env.ITX.get();\n const status = await project.workspaces.get(notesWorkspacePath).git.status();\n const mount = status.mounts.find((candidate) => candidate.path === notesRepoPath);\n return (mount?.changes || []).map((change) => change.path);\n },\n commit: async (input) => {\n using project = await this.env.ITX.get();\n await project.workspaces.get(notesWorkspacePath).git.commit(input);\n },\n },\n });\n }\n\n /** Project-worker event delivery calls this after a durable event commits\n * on the notes workspace stream. Catch-up owns validation, ordering,\n * checkpointing, and dedupe. */\n async syncEvent(event: StreamEvent): Promise<void> {\n if (event.path !== notesWorkspacePath) return;\n const registry = await this.registry();\n await registry.catchUp(\"notes\");\n registry.refreshLive();\n }\n}\n"],"mappings":"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"}
|