flowviant 0.104.0 → 0.106.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/bin/lib/agentCards.mjs +9 -2
- package/bin/lib/agentTaskKinds.mjs +49 -12
- package/bin/lib/agentTaskTypes.mjs +135 -0
- package/bin/lib/artifactContracts.mjs +153 -0
- package/bin/lib/artifactPolicy.mjs +1 -0
- package/bin/lib/artifacts.mjs +2 -1
- package/bin/lib/cardThread.mjs +89 -0
- package/bin/lib/claudePosture.mjs +5 -2
- package/bin/lib/knowledge.mjs +1 -1
- package/bin/lib/knowledgeFiles.mjs +4 -2
- package/bin/lib/knowledgeLibrary.mjs +80 -30
- package/bin/lib/prompts.mjs +169 -107
- package/bin/lib/workAgentTurnExecution.mjs +12 -4
- package/bin/lib/workAgentTurnOutcome.mjs +20 -8
- package/package.json +1 -1
package/bin/lib/agentCards.mjs
CHANGED
|
@@ -37,6 +37,7 @@
|
|
|
37
37
|
*/
|
|
38
38
|
|
|
39
39
|
import { appendFileSync, readFileSync } from 'node:fs';
|
|
40
|
+
import { CARD_THREAD_RENDER_MAX } from './cardThread.mjs';
|
|
40
41
|
|
|
41
42
|
/**
|
|
42
43
|
* How many card specs one agent may accumulate.
|
|
@@ -49,8 +50,14 @@ import { appendFileSync, readFileSync } from 'node:fs';
|
|
|
49
50
|
export const MAX_STASHED_CARDS = 40;
|
|
50
51
|
/** The most one spec may contribute. A brief is written by whoever filed the
|
|
51
52
|
* card and the server caps it, but this file is composed into a prompt and a
|
|
52
|
-
* bound it owns is a bound that cannot be argued away upstream.
|
|
53
|
-
|
|
53
|
+
* bound it owns is a bound that cannot be argued away upstream.
|
|
54
|
+
*
|
|
55
|
+
* 8,000 is the record's brief ceiling; since 0.106.0 the spec also carries
|
|
56
|
+
* the card's discussion, so the bound grows by the most a turn's discussion
|
|
57
|
+
* can render to (`CARD_THREAD_RENDER_MAX`). The discussion is printed LAST,
|
|
58
|
+
* and a stash that cut it would hand the pre-review a thread missing its
|
|
59
|
+
* newest words — the ones the agent read most recently. */
|
|
60
|
+
export const MAX_SPEC_CHARS = 8_000 + CARD_THREAD_RENDER_MAX;
|
|
54
61
|
|
|
55
62
|
/**
|
|
56
63
|
* Write down the spec this turn is about to hand the agent.
|
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* A CARD'S KIND — the daemon's one table of what each kind IS (0.97.0):
|
|
3
|
-
* `code` (the default), `design` and `research
|
|
3
|
+
* `code` (the default), `design` and `research`; since 0.105.0 also `model`
|
|
4
|
+
* (the 3D-model kind — not an AI-model pin, which is `job.model`) and `deck`
|
|
5
|
+
* (the presentation kind — not the review deck). Every reader of a kind goes
|
|
4
6
|
* through this module: the prompts that print it and pick a contract
|
|
5
|
-
* (prompts.mjs, which keeps the strings
|
|
7
|
+
* (prompts.mjs, which keeps the strings, with the 3D-model and presentation
|
|
8
|
+
* contracts and the shared tail in artifactContracts.mjs), the agent lane
|
|
9
|
+
* that picks a posture,
|
|
6
10
|
* refuses a runtime (workAgentTurnExecution.mjs) and checks the artifact
|
|
7
11
|
* (workAgentTurnOutcome.mjs), and the turn runner that fences the non-code
|
|
8
12
|
* postures (runTurn.mjs and claudePosture.mjs, via turnProfile.mjs).
|
|
@@ -23,10 +27,15 @@
|
|
|
23
27
|
* The kinds. Per kind:
|
|
24
28
|
* - `posture`: the turn profile the card runs under (turnProfile.mjs);
|
|
25
29
|
* - `artifact`: null for code (it delivers commits), else what proves a
|
|
26
|
-
* delivery — `match` over the artifact file name
|
|
27
|
-
*
|
|
30
|
+
* delivery — `match` over the artifact file name, an optional `companion`
|
|
31
|
+
* that must ALSO stand beside it (the 3D-model kind's model file: an HTML
|
|
32
|
+
* viewer alone is a page, not a model), and the `missing` sentence a turn
|
|
33
|
+
* settles `nothing` with when the proof was not written;
|
|
28
34
|
* - `offRuntime(rt)`: the sentence when the chosen CLI cannot run the posture.
|
|
29
35
|
*/
|
|
36
|
+
/** Every non-code kind runs on Claude: the one CLI that declares their postures. */
|
|
37
|
+
const CLAUDE_ONLY = 'mockup, 3D model, presentation and write-up cards run on Claude on this machine';
|
|
38
|
+
|
|
30
39
|
export const AGENT_TASK_KINDS = Object.freeze({
|
|
31
40
|
code: Object.freeze({
|
|
32
41
|
posture: 'build',
|
|
@@ -39,7 +48,32 @@ export const AGENT_TASK_KINDS = Object.freeze({
|
|
|
39
48
|
match: /\.html?$/i,
|
|
40
49
|
missing: 'the turn ended without writing a mockup under .flowviant/artifacts/',
|
|
41
50
|
}),
|
|
42
|
-
offRuntime: () =>
|
|
51
|
+
offRuntime: () => CLAUDE_ONLY,
|
|
52
|
+
}),
|
|
53
|
+
/**
|
|
54
|
+
* THE 3D-MODEL KIND (0.105.0): a viewable bundle — an HTML viewer and the
|
|
55
|
+
* model files beside it. Under the design fence (read the repo, write only
|
|
56
|
+
* `.flowviant/artifacts/**`): a model is a picture of an asset, and
|
|
57
|
+
* committing it is a separate code card a person raises at Accept.
|
|
58
|
+
*/
|
|
59
|
+
model: Object.freeze({
|
|
60
|
+
posture: 'design',
|
|
61
|
+
artifact: Object.freeze({
|
|
62
|
+
match: /\.html?$/i,
|
|
63
|
+
companion: /\.(gltf|glb|obj)$/i,
|
|
64
|
+
missing:
|
|
65
|
+
'the turn ended without writing a model bundle (an HTML viewer and a .gltf, .glb or .obj file) under .flowviant/artifacts/',
|
|
66
|
+
}),
|
|
67
|
+
offRuntime: () => CLAUDE_ONLY,
|
|
68
|
+
}),
|
|
69
|
+
/** THE PRESENTATION KIND (0.105.0): one HTML slide deck, under the design fence. */
|
|
70
|
+
deck: Object.freeze({
|
|
71
|
+
posture: 'design',
|
|
72
|
+
artifact: Object.freeze({
|
|
73
|
+
match: /\.html?$/i,
|
|
74
|
+
missing: 'the turn ended without writing a deck under .flowviant/artifacts/',
|
|
75
|
+
}),
|
|
76
|
+
offRuntime: () => CLAUDE_ONLY,
|
|
43
77
|
}),
|
|
44
78
|
research: Object.freeze({
|
|
45
79
|
posture: 'research',
|
|
@@ -47,7 +81,7 @@ export const AGENT_TASK_KINDS = Object.freeze({
|
|
|
47
81
|
match: /\.md$/i,
|
|
48
82
|
missing: 'the turn ended without writing a write-up under .flowviant/artifacts/',
|
|
49
83
|
}),
|
|
50
|
-
offRuntime: () =>
|
|
84
|
+
offRuntime: () => CLAUDE_ONLY,
|
|
51
85
|
}),
|
|
52
86
|
});
|
|
53
87
|
|
|
@@ -85,9 +119,12 @@ export const unknownAgentTaskKind = (v) =>
|
|
|
85
119
|
/** The table entry for a kind value, read the printing way (unknown → code). */
|
|
86
120
|
export const agentTaskKind = (v) => AGENT_TASK_KINDS[agentTaskKindOf(v)];
|
|
87
121
|
|
|
88
|
-
/** The postures only a card's kind selects (every kind's but code's)
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
.
|
|
93
|
-
)
|
|
122
|
+
/** The postures only a card's kind selects (every kind's but code's), each
|
|
123
|
+
* once: the 3D-model and presentation kinds reuse the design posture. */
|
|
124
|
+
export const KIND_POSTURES = Object.freeze([
|
|
125
|
+
...new Set(
|
|
126
|
+
Object.values(AGENT_TASK_KINDS)
|
|
127
|
+
.map((k) => k.posture)
|
|
128
|
+
.filter((p) => p !== 'build')
|
|
129
|
+
),
|
|
130
|
+
]);
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A CARD'S TYPE — the daemon's one table of what each type ASKS (0.106.0).
|
|
3
|
+
*
|
|
4
|
+
* A type is the kind of WORK a card asks for — a Bug fix, a Refactor, a Code
|
|
5
|
+
* review — a flavour over the card's KIND (agentTaskKinds.mjs), which stays the
|
|
6
|
+
* end product and the posture the turn runs under. Every type runs as exactly
|
|
7
|
+
* one kind (`kind` below, the app's `TASK_TYPE_KIND`), and adds the one thing
|
|
8
|
+
* the kind cannot say: a bug fix reproduces first, a refactor must not change
|
|
9
|
+
* behaviour, a code review changes nothing.
|
|
10
|
+
*
|
|
11
|
+
* The server sends the id — `agentTurnJobs[].task.taskType` and
|
|
12
|
+
* `agentPlanJobs[].tasks[].taskType` — and only to a daemon at 0.106.0 (below
|
|
13
|
+
* it the server parks a typed card's turn rather than hand it to a daemon that
|
|
14
|
+
* would drop the key). THE DAEMON OWNS THE WORDS: each type's `contract` is
|
|
15
|
+
* the one sentence an agent is held to, printed inside THE CARD right after
|
|
16
|
+
* the kind line (`AGENT_TASK_SPEC`), so the spec stashed for the pre-review
|
|
17
|
+
* carries it too. Each sentence is true to what its kind's posture can do: a
|
|
18
|
+
* code type may build and test; a write-up type reads and writes one Markdown
|
|
19
|
+
* file; a mockup type writes under .flowviant/artifacts/ and nothing else.
|
|
20
|
+
*
|
|
21
|
+
* `label` is the app's `TASK_TYPE_LABEL`, the word a person picked. The ids
|
|
22
|
+
* (in the app's picker order), their kinds and their labels are held to the
|
|
23
|
+
* app's `taskType.ts` by the release gate (`scripts/check-app-parity.mjs`,
|
|
24
|
+
* rule 6): a type the server sends that this table lacks is printed as
|
|
25
|
+
* nothing, and one it has that the server never sends is dead.
|
|
26
|
+
*
|
|
27
|
+
* Pure data and pure functions; its one import is the kind table's reader.
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
import { agentTaskKindOf } from './agentTaskKinds.mjs';
|
|
31
|
+
|
|
32
|
+
export const AGENT_TASK_TYPES = Object.freeze({
|
|
33
|
+
feature: Object.freeze({
|
|
34
|
+
label: 'Feature',
|
|
35
|
+
kind: 'code',
|
|
36
|
+
contract: 'new behaviour: build it, and cover it with tests where the repository has them.',
|
|
37
|
+
}),
|
|
38
|
+
fix: Object.freeze({
|
|
39
|
+
label: 'Bug fix',
|
|
40
|
+
kind: 'code',
|
|
41
|
+
contract:
|
|
42
|
+
'reproduce it first, with a failing test where the repository has tests, then make that test pass.',
|
|
43
|
+
}),
|
|
44
|
+
tests: Object.freeze({
|
|
45
|
+
label: 'Tests',
|
|
46
|
+
kind: 'code',
|
|
47
|
+
contract:
|
|
48
|
+
'add tests for code that already exists and change no production code; if a test exposes a bug, ' +
|
|
49
|
+
'say so in your summary instead of fixing it.',
|
|
50
|
+
}),
|
|
51
|
+
refactor: Object.freeze({
|
|
52
|
+
label: 'Refactor',
|
|
53
|
+
kind: 'code',
|
|
54
|
+
contract: 'behaviour must not change; if an existing test would have to change, stop and ask.',
|
|
55
|
+
}),
|
|
56
|
+
infra: Object.freeze({
|
|
57
|
+
label: 'Infra & deploy',
|
|
58
|
+
kind: 'code',
|
|
59
|
+
contract: 'change the configuration in the repository; never run a deploy or touch a live environment.',
|
|
60
|
+
}),
|
|
61
|
+
automation: Object.freeze({
|
|
62
|
+
label: 'Automation',
|
|
63
|
+
kind: 'code',
|
|
64
|
+
contract:
|
|
65
|
+
"add the scheduled job to the repository (a CI schedule, or a script and its schedule) so it runs " +
|
|
66
|
+
"where the repository's CI runs; do not run it.",
|
|
67
|
+
}),
|
|
68
|
+
review: Object.freeze({
|
|
69
|
+
label: 'Code review',
|
|
70
|
+
kind: 'research',
|
|
71
|
+
contract:
|
|
72
|
+
'read the code the card names and write up what is wrong, by file and line, most serious first; ' +
|
|
73
|
+
'change nothing.',
|
|
74
|
+
}),
|
|
75
|
+
ui: Object.freeze({
|
|
76
|
+
label: 'UI design',
|
|
77
|
+
kind: 'design',
|
|
78
|
+
contract: "draw the screens and flows the card names as the mockup page, in this product's own components and words.",
|
|
79
|
+
}),
|
|
80
|
+
prototype: Object.freeze({
|
|
81
|
+
label: 'Prototype',
|
|
82
|
+
kind: 'design',
|
|
83
|
+
contract: 'make every control in the flow work, with realistic sample data, in the one page.',
|
|
84
|
+
}),
|
|
85
|
+
slides: Object.freeze({
|
|
86
|
+
label: 'Slides',
|
|
87
|
+
kind: 'deck',
|
|
88
|
+
contract: 'one deck for the audience and the length the card asks for, one point to a slide.',
|
|
89
|
+
}),
|
|
90
|
+
model3d: Object.freeze({
|
|
91
|
+
label: '3D model',
|
|
92
|
+
kind: 'model',
|
|
93
|
+
contract:
|
|
94
|
+
'model the object the card describes as the viewable bundle, at the scale and in the units of any ' +
|
|
95
|
+
'3D assets the repository already has.',
|
|
96
|
+
}),
|
|
97
|
+
vector: Object.freeze({
|
|
98
|
+
label: 'Icons & vector',
|
|
99
|
+
kind: 'design',
|
|
100
|
+
contract:
|
|
101
|
+
'write each icon as its own .svg file directly in .flowviant/artifacts/, and make the mockup page ' +
|
|
102
|
+
'the sheet that shows them all, inline, at the sizes they are used.',
|
|
103
|
+
}),
|
|
104
|
+
research: Object.freeze({
|
|
105
|
+
label: 'Research',
|
|
106
|
+
kind: 'research',
|
|
107
|
+
contract: 'answer the question the card asks, leading with the answer and citing what you read.',
|
|
108
|
+
}),
|
|
109
|
+
writing: Object.freeze({
|
|
110
|
+
label: 'Writing',
|
|
111
|
+
kind: 'research',
|
|
112
|
+
contract: 'write the document the card asks for, for the reader it names, in words that reader uses.',
|
|
113
|
+
}),
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* A CARD'S TYPE, READ THE ONE WAY THE DAEMON READS IT — a type this table
|
|
118
|
+
* knows, else null. ABSENT IS NO TYPE (every card before 0.106.0, and every
|
|
119
|
+
* job from an older server), and an unknown id (a newer server, a typo) is
|
|
120
|
+
* dropped rather than guessed at: a type only adds a sentence, so dropping one
|
|
121
|
+
* leaves the card running on its kind, which is what it always did.
|
|
122
|
+
*/
|
|
123
|
+
export const agentTaskTypeOf = (v) => (typeof v === 'string' && Object.hasOwn(AGENT_TASK_TYPES, v) ? v : null);
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* The type of a card AS IT WILL BE PRINTED — null unless the card's type is
|
|
127
|
+
* known here AND runs as the card's own kind. The server sends a type only on
|
|
128
|
+
* its kind, but the card is a document any editor writes; a Bug fix sentence
|
|
129
|
+
* ("with a failing test") under a mockup's posture is a contract the turn
|
|
130
|
+
* cannot keep, so a type on another kind is printed as nothing.
|
|
131
|
+
*/
|
|
132
|
+
export const agentTaskTypeFor = (task) => {
|
|
133
|
+
const type = agentTaskTypeOf(task?.taskType);
|
|
134
|
+
return type && AGENT_TASK_TYPES[type].kind === agentTaskKindOf(task?.taskKind) ? type : null;
|
|
135
|
+
};
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* THE 3D-MODEL AND PRESENTATION CARDS' CONTRACTS (2026-09-27, 0.105.0).
|
|
3
|
+
*
|
|
4
|
+
* Two more kinds that hand back a file rather than a change, beside the design
|
|
5
|
+
* and research contracts in prompts.mjs (which is past its size and keeps
|
|
6
|
+
* those two). `prompts.mjs` imports these into `AGENT_CONTRACTS`; the kind
|
|
7
|
+
* table (agentTaskKinds.mjs) runs both under the `design` posture — read the
|
|
8
|
+
* repo, write only `.flowviant/artifacts/**`, no web, no shell beyond `ls` —
|
|
9
|
+
* so "change no repository file" is enforced, not asked.
|
|
10
|
+
*
|
|
11
|
+
* THE SAME BONES AS THE DESIGN CONTRACT, deliberately: its header, its rule 3
|
|
12
|
+
* (commit nothing), its rule 4 (stop and ask) and its final JSON — one shape
|
|
13
|
+
* `parseTurnResult`, the settle, the board and the review deck already read.
|
|
14
|
+
* The tail is spelled ONCE, in `ARTIFACT_CONTRACT_TAIL`, and every
|
|
15
|
+
* file-handing contract renders it — these two, and the design and research
|
|
16
|
+
* contracts in prompts.mjs (whose bytes `taskKind.test.mjs` pins unchanged by
|
|
17
|
+
* the move), so no two of them can drift apart.
|
|
18
|
+
*
|
|
19
|
+
* "model" here is the 3D-model KIND (not an AI-model pin, `job.model`), and
|
|
20
|
+
* "deck" the presentation kind (not the review deck).
|
|
21
|
+
*
|
|
22
|
+
* Pure strings: no imports, no environment, no I/O.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Rule 3, rule 4 and the final JSON a file-handing contract ends with.
|
|
27
|
+
* `ask` is rule 4's body after its number (the decision only a person can
|
|
28
|
+
* make); `summary` is the delivered shape's summary gloss.
|
|
29
|
+
*/
|
|
30
|
+
export const ARTIFACT_CONTRACT_TAIL = ({ ask, summary }) => `3. CHANGE NO REPOSITORY FILE AND COMMIT NOTHING. You can only write under
|
|
31
|
+
.flowviant/artifacts/, and the daemon keeps that directory out of git.
|
|
32
|
+
|
|
33
|
+
4. ${ask}
|
|
34
|
+
|
|
35
|
+
END YOUR TURN WITH ONE JSON OBJECT AND NOTHING AFTER IT, in a \`\`\`json fence:
|
|
36
|
+
|
|
37
|
+
\`\`\`json
|
|
38
|
+
{
|
|
39
|
+
"status": "delivered",
|
|
40
|
+
"summary": "${summary}",
|
|
41
|
+
"progress": "two or three sentences on what you have done in this run SO FAR, across every card"
|
|
42
|
+
}
|
|
43
|
+
\`\`\`
|
|
44
|
+
|
|
45
|
+
or, if you are stopping to ask:
|
|
46
|
+
|
|
47
|
+
\`\`\`json
|
|
48
|
+
{
|
|
49
|
+
"status": "blocked",
|
|
50
|
+
"progress": "two or three sentences on what you have done in this run SO FAR, across every card",
|
|
51
|
+
"question": "the specific thing you need decided, in one or two sentences"
|
|
52
|
+
}
|
|
53
|
+
\`\`\`
|
|
54
|
+
|
|
55
|
+
"progress" is REQUIRED on both shapes and REPLACES the previous one whole —
|
|
56
|
+
past tense, plain sentences, no card ids.`;
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* THE 3D-MODEL CARD. The product is a model a person can turn around in
|
|
60
|
+
* Review: one folder holding a three.js viewer and the model files it loads
|
|
61
|
+
* by relative URL — the bundle shape the artifact scan keeps (relative paths
|
|
62
|
+
* to depth four, 0.99.0) and the library keeps as one versioned folder. The
|
|
63
|
+
* daemon calls it delivered only when an `.html` AND a `.gltf`/`.glb`/`.obj`
|
|
64
|
+
* stand under `.flowviant/artifacts/` (agentTaskKinds.mjs).
|
|
65
|
+
*
|
|
66
|
+
* TEXT, BECAUSE TEXT IS ALL THIS POSTURE WRITES (2026-09-27, a call the owner
|
|
67
|
+
* may overrule). The kind runs under the `design` posture: Read, `ls`, and
|
|
68
|
+
* `Edit(.flowviant/artifacts/**)` — Claude's Write/Edit tools write text, and
|
|
69
|
+
* there is no shell that could produce bytes. The spec's first wording asked
|
|
70
|
+
* for glTF with a `.bin`, or a `.glb`, and `.png`/`.jpg` textures: outputs
|
|
71
|
+
* this card cannot make. So the contract asks for OBJ + `.mtl` (text, fetched
|
|
72
|
+
* by relative URL from the artifact host, which the viewer's `connect-src`
|
|
73
|
+
* admits) and SVG or procedural textures (`img-src` admits the host). NOT a
|
|
74
|
+
* `.gltf` with its buffer as a `data:` URI: three.js fetches that buffer, and
|
|
75
|
+
* the artifact CSP's `connect-src` names only the artifact host
|
|
76
|
+
* (apps/api/src/lib/artifactDelivery.ts), so the review viewer would refuse
|
|
77
|
+
* it. Binary files are still accepted — the artifact policy, the proof, the
|
|
78
|
+
* library and the 20 MB cap all keep them — for a folder that already holds
|
|
79
|
+
* one; widening the posture to write them is the owner's decision.
|
|
80
|
+
*/
|
|
81
|
+
export const SYSTEM_AGENT_MODEL = `You are the human's own Claude, working one 3D MODEL card in a git worktree of
|
|
82
|
+
their repository. Nobody is watching this run. You have the repo and nothing
|
|
83
|
+
else — no project tools, no board, no chat.
|
|
84
|
+
|
|
85
|
+
A 3D MODEL CARD HANDS BACK A MODEL YOU CAN LOOK AT, NOT A CHANGE. Nothing you
|
|
86
|
+
do here edits the product: the person turns the model around, and decides.
|
|
87
|
+
|
|
88
|
+
WHAT TO DO:
|
|
89
|
+
|
|
90
|
+
1. Read the repository first — the existing 3D assets and the scene they sit
|
|
91
|
+
in: their scale and units, their up axis, their style, their materials and
|
|
92
|
+
texture sizes. The model must fit THIS project, not a stock asset library.
|
|
93
|
+
|
|
94
|
+
2. Write ONE folder, .flowviant/artifacts/<kebab-name>/ — a short kebab-case
|
|
95
|
+
name for what it is (for example .flowviant/artifacts/reading-chair/) —
|
|
96
|
+
holding:
|
|
97
|
+
- index.html, a viewer: three.js loaded from cdn.jsdelivr.net/npm or
|
|
98
|
+
unpkg.com, with orbit controls, loading the model by a RELATIVE URL
|
|
99
|
+
(./reading-chair.obj — never a path outside the folder);
|
|
100
|
+
- the model, written as TEXT: a .obj with its .mtl, each loaded by a
|
|
101
|
+
relative URL;
|
|
102
|
+
- its textures as .svg files referenced by relative paths, or drawn by
|
|
103
|
+
the viewer itself (procedural materials, canvas textures).
|
|
104
|
+
You can write only text files here. A .glb, .bin, .png or .jpg is kept
|
|
105
|
+
and shown if one already stands in the folder, but this card cannot make
|
|
106
|
+
one — never plan on one. Nothing else may load from the network, and
|
|
107
|
+
nothing can be sent. Keep every file under 2 MB — the viewer, the .obj,
|
|
108
|
+
the .mtl, each texture.
|
|
109
|
+
|
|
110
|
+
${ARTIFACT_CONTRACT_TAIL({
|
|
111
|
+
ask: `If you cannot model it without a DECISION only a person can make — which of
|
|
112
|
+
two directions, what size or style, which asset it stands beside — STOP AND
|
|
113
|
+
ASK. Do not guess. A question costs one reply; a guessed model costs a review.`,
|
|
114
|
+
summary: 'one or two sentences on what the model shows, and the folder it is in',
|
|
115
|
+
})}`;
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* THE PRESENTATION CARD. One self-contained HTML deck under the design
|
|
119
|
+
* card's inline and CDN rules, navigable by keyboard and on screen, and
|
|
120
|
+
* printable one slide per page — so "send it as a PDF" is the browser's
|
|
121
|
+
* Print, never a converter this machine would have to run.
|
|
122
|
+
*/
|
|
123
|
+
export const SYSTEM_AGENT_DECK = `You are the human's own Claude, working one PRESENTATION card in a git worktree
|
|
124
|
+
of their repository. Nobody is watching this run. You have the repo and nothing
|
|
125
|
+
else — no project tools, no board, no chat.
|
|
126
|
+
|
|
127
|
+
A PRESENTATION CARD HANDS BACK A DECK, NOT A CHANGE. Nothing you do here edits
|
|
128
|
+
the product: the person pages through it, and decides.
|
|
129
|
+
|
|
130
|
+
WHAT TO DO:
|
|
131
|
+
|
|
132
|
+
1. Read what the deck is about first — the repository, the real product, its
|
|
133
|
+
brand and its own words. The deck must sound and look like THIS project,
|
|
134
|
+
not like a template.
|
|
135
|
+
|
|
136
|
+
2. Write ONE self-contained HTML file directly in .flowviant/artifacts/ — a
|
|
137
|
+
short kebab-case name for the talk (for example
|
|
138
|
+
.flowviant/artifacts/q3-roadmap.html). Inline CSS (a Google Fonts
|
|
139
|
+
stylesheet may load). Scripts may be inline or loaded from
|
|
140
|
+
cdnjs.cloudflare.com, cdn.jsdelivr.net/npm or unpkg.com, and from nowhere
|
|
141
|
+
else; nothing else may load from the network and nothing can be sent, so
|
|
142
|
+
images are data: URIs or inline SVG. Each slide is a <section>; the arrow
|
|
143
|
+
keys and on-screen previous/next buttons move between them, and a counter
|
|
144
|
+
says which slide of how many is showing. Add @media print rules that put
|
|
145
|
+
one slide on each page (break-after: page), so the browser prints it to a
|
|
146
|
+
PDF. Keep it under 2 MB.
|
|
147
|
+
|
|
148
|
+
${ARTIFACT_CONTRACT_TAIL({
|
|
149
|
+
ask: `If you cannot write it without a DECISION only a person can make — who the
|
|
150
|
+
audience is, how long it runs, what it must argue — STOP AND ASK. Do not
|
|
151
|
+
guess. A question costs one reply; a guessed deck costs a review.`,
|
|
152
|
+
summary: 'one or two sentences on what the deck covers, and the file it is in',
|
|
153
|
+
})}`;
|
|
@@ -39,6 +39,7 @@ export const ARTIFACT_TYPES = {
|
|
|
39
39
|
pdf: { mime: 'application/pdf', text: false },
|
|
40
40
|
gltf: { mime: 'model/gltf+json', text: true },
|
|
41
41
|
obj: { mime: 'model/obj', text: true },
|
|
42
|
+
mtl: { mime: 'model/mtl', text: true },
|
|
42
43
|
glb: { mime: 'model/gltf-binary', text: false },
|
|
43
44
|
bin: { mime: 'application/octet-stream', text: false },
|
|
44
45
|
};
|
package/bin/lib/artifacts.mjs
CHANGED
|
@@ -28,7 +28,8 @@
|
|
|
28
28
|
*
|
|
29
29
|
* ── THE BOUNDS ──
|
|
30
30
|
*
|
|
31
|
-
* Regular files only, DEPTH
|
|
31
|
+
* Regular files only, to DEPTH FOUR (relative paths, 0.99.0 — a bundle's
|
|
32
|
+
* sidecars), `lstat` and never followed: a symlink an
|
|
32
33
|
* agent planted at `.flowviant/artifacts/x.txt -> ~/.ssh/id_ed25519` is
|
|
33
34
|
* reported as nothing, and the read itself opens with `O_NOFOLLOW` so a swap
|
|
34
35
|
* between the lstat and the open cannot turn one into a follow. The directory
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A CARD'S DISCUSSION, AS THE WIRE HANDS IT OVER (0.106.0) — the owner,
|
|
3
|
+
* 2026-09-27: "yes agents should read card comments".
|
|
4
|
+
*
|
|
5
|
+
* The server relays a card's thread (people's comments AND agents' own notes,
|
|
6
|
+
* replies and delivery cards) onto the jobs that work or plan it:
|
|
7
|
+
* `agentTurnJobs[].cardThread` (the one card a turn is about, named by
|
|
8
|
+
* `taskId` and `title`) and `agentPlanJobs[].tasks[].thread`. Each is
|
|
9
|
+
* `{ entries: [{ who, at, text }], omitted }`, oldest first — the newest run
|
|
10
|
+
* of the thread that fits the server's budget, with `omitted` counting the
|
|
11
|
+
* older entries it left out.
|
|
12
|
+
*
|
|
13
|
+
* THE WIRE IS NOT TRUSTED. The server bounded it (`CARD_THREAD_BUDGET` in the
|
|
14
|
+
* app's agent-runner.schema.ts); this module reads it again, entry by entry,
|
|
15
|
+
* and applies the SAME numbers — a copy the release gate holds to the server's
|
|
16
|
+
* (`scripts/check-app-parity.mjs`, rule 5). A malformed entry is dropped
|
|
17
|
+
* alone; a name or a time is one bounded line (a line break would forge a
|
|
18
|
+
* second entry); a text past its cap is cut and marked; entries past the
|
|
19
|
+
* budget are dropped from the OLDEST end and counted, so an agent is never
|
|
20
|
+
* handed a thread that silently starts in the middle.
|
|
21
|
+
*
|
|
22
|
+
* Absent, empty or wholly malformed reads as null, and a null thread prints
|
|
23
|
+
* NOTHING — which is why every prompt for a card nobody discussed (and every
|
|
24
|
+
* job from a server older than 0.106.0) stays byte-identical.
|
|
25
|
+
*
|
|
26
|
+
* Pure data and pure functions: no imports, no environment, no I/O — prompts
|
|
27
|
+
* imports it, and prompts is strings.
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
/** The server's `CARD_THREAD_BUDGET`, row for row: a TURN reads one card, a
|
|
31
|
+
* PLAN glances at many. */
|
|
32
|
+
export const CARD_THREAD_BUDGET = Object.freeze({
|
|
33
|
+
turn: Object.freeze({ entries: 30, chars: 6000, entryChars: 1500 }),
|
|
34
|
+
plan: Object.freeze({ entries: 6, chars: 1200, entryChars: 400 }),
|
|
35
|
+
});
|
|
36
|
+
/** The server's `CARD_THREAD_WHO_MAX` — a name is a label. */
|
|
37
|
+
export const CARD_THREAD_WHO_MAX = 80;
|
|
38
|
+
/** The server's `CARD_THREAD_AT_MAX` — an ISO time, with room. */
|
|
39
|
+
export const CARD_THREAD_AT_MAX = 40;
|
|
40
|
+
/** A count is a number a prompt prints; past this it is not a count. */
|
|
41
|
+
const OMITTED_MAX = 1_000_000;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* THE MOST ONE TURN'S DISCUSSION CAN ADD TO A CARD SPEC, rendered — so the
|
|
45
|
+
* spec stash (agentCards.mjs `MAX_SPEC_CHARS`) can hold a card with its
|
|
46
|
+
* discussion whole, and the pre-review reads what the agent read. The text is
|
|
47
|
+
* at most `chars`; the renderer adds at most two characters and a newline per
|
|
48
|
+
* text line (<= 3x the text), a header line per entry, and two lines of its
|
|
49
|
+
* own. `cardThread.test.mjs` renders the worst case against it.
|
|
50
|
+
*/
|
|
51
|
+
export const CARD_THREAD_RENDER_MAX =
|
|
52
|
+
3 * CARD_THREAD_BUDGET.turn.chars +
|
|
53
|
+
CARD_THREAD_BUDGET.turn.entries * (CARD_THREAD_WHO_MAX + CARD_THREAD_AT_MAX + 10) +
|
|
54
|
+
200;
|
|
55
|
+
|
|
56
|
+
const oneLine = (v, max) => String(v).replace(/[\r\n]+/g, ' ').trim().slice(0, max);
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* A thread off the wire, re-bounded at `budget`'s row ('turn' | 'plan'; an
|
|
60
|
+
* unknown row is the tighter plan one), or null when there is nothing in it to
|
|
61
|
+
* print. Never throws.
|
|
62
|
+
*/
|
|
63
|
+
export function readCardThread(raw, budget = 'turn') {
|
|
64
|
+
const b = CARD_THREAD_BUDGET[budget] ?? CARD_THREAD_BUDGET.plan;
|
|
65
|
+
if (!raw || typeof raw !== 'object' || !Array.isArray(raw.entries)) return null;
|
|
66
|
+
const good = [];
|
|
67
|
+
for (const e of raw.entries) {
|
|
68
|
+
if (!e || typeof e !== 'object') continue;
|
|
69
|
+
if (typeof e.who !== 'string' || typeof e.at !== 'string' || typeof e.text !== 'string') continue;
|
|
70
|
+
const whole = e.text.replace(/\r\n?/g, '\n').trim();
|
|
71
|
+
if (!whole) continue;
|
|
72
|
+
good.push({
|
|
73
|
+
who: oneLine(e.who, CARD_THREAD_WHO_MAX) || 'member',
|
|
74
|
+
at: oneLine(e.at, CARD_THREAD_AT_MAX),
|
|
75
|
+
text: whole.length > b.entryChars ? `${whole.slice(0, b.entryChars - 1)}…` : whole,
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
// The newest contiguous run that fits — the server's own walk.
|
|
79
|
+
const kept = [];
|
|
80
|
+
let chars = 0;
|
|
81
|
+
for (let i = good.length - 1; i >= 0 && kept.length < b.entries; i--) {
|
|
82
|
+
if (chars + good[i].text.length > b.chars) break;
|
|
83
|
+
chars += good[i].text.length;
|
|
84
|
+
kept.push(good[i]);
|
|
85
|
+
}
|
|
86
|
+
if (kept.length === 0) return null;
|
|
87
|
+
const sent = Number.isInteger(raw.omitted) && raw.omitted > 0 ? Math.min(raw.omitted, OMITTED_MAX) : 0;
|
|
88
|
+
return { entries: kept.reverse(), omitted: sent + (good.length - kept.length) };
|
|
89
|
+
}
|
|
@@ -219,8 +219,11 @@ export function consultPermFor(knowledgeDir) {
|
|
|
219
219
|
* `**` AND NOT `*`, measured the same day: `Edit(.flowviant/artifacts/*)`
|
|
220
220
|
* let a Write to `.flowviant/artifacts/sub/b.html` through exactly as `**`
|
|
221
221
|
* did, so the single-level spelling is not narrower on this CLI and would
|
|
222
|
-
* only read as if it were.
|
|
223
|
-
*
|
|
222
|
+
* only read as if it were. So where a file may sit under the artifacts
|
|
223
|
+
* directory is the prompt's to say, never the posture's: the design and
|
|
224
|
+
* research contracts say "no subfolders" (written when the scan kept
|
|
225
|
+
* top-level files only; it walks to depth four since 0.99.0), and the
|
|
226
|
+
* 3D-model contract, under this same posture, asks for one folder.
|
|
224
227
|
*
|
|
225
228
|
* THEY ARE NOT THE SAME READER, and the difference is the network:
|
|
226
229
|
*
|
package/bin/lib/knowledge.mjs
CHANGED
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
*
|
|
60
60
|
* This module is the ROSTER DRIVER alone: the rev held in memory, the
|
|
61
61
|
* serialised sync, the backoff. The pure naming, path and catalog rules —
|
|
62
|
-
* including the kept library's
|
|
62
|
+
* including the kept library's four subdirectories — are knowledgeLibrary.mjs;
|
|
63
63
|
* the disk (the sync itself, the marker, `knowledgeDirFor`) is
|
|
64
64
|
* knowledgeFiles.mjs; the `/fleet/knowledge` and `/fleet/library` download is
|
|
65
65
|
* knowledgeFetch.mjs. Callers import the owner they need.
|
|
@@ -288,7 +288,7 @@ export async function syncKnowledge({ checkoutDir, manifest, fetchFile, maxBytes
|
|
|
288
288
|
/**
|
|
289
289
|
* THE KEPT LIBRARY'S HALF OF A SYNC: each item into its subdirectory (fetched
|
|
290
290
|
* only when its sha differs, written atomically, the bytes checked against the
|
|
291
|
-
* manifest), every other entry in the
|
|
291
|
+
* manifest), every other entry in the library subdirectories removed, and
|
|
292
292
|
* `LIBRARY.md` regenerated from the items that are on disk AS THE MANIFEST
|
|
293
293
|
* NAMES THEM — the entry and every bundle file current (`isCurrent`,
|
|
294
294
|
* knowledgeFileSync.mjs). A download that fails keeps whatever copy was there
|
|
@@ -296,7 +296,9 @@ export async function syncKnowledge({ checkoutDir, manifest, fetchFile, maxBytes
|
|
|
296
296
|
* item over the cap is refused for this rev and left out of the catalog.
|
|
297
297
|
*/
|
|
298
298
|
async function syncLibrary({ dir, items, fetchFile, maxBytes, keep, result }) {
|
|
299
|
-
|
|
299
|
+
// One set per library folder, from the one table — a fourth folder added
|
|
300
|
+
// there is synced and swept here with no second list to miss.
|
|
301
|
+
const wanted = Object.fromEntries(Object.values(LIBRARY_DIRS).map((d) => [d, new Set()]));
|
|
300
302
|
const onDisk = [];
|
|
301
303
|
for (const kind of Object.keys(LIBRARY_DIRS)) {
|
|
302
304
|
if (items.some((it) => it.kind === kind)) {
|