flint-agent 1.14.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/.env.example +108 -0
- package/CHANGELOG.md +55 -0
- package/FEATURES.md +298 -0
- package/LICENSE +21 -0
- package/README.md +435 -0
- package/bin/flint.js +47 -0
- package/config/classifier-prompt.md +218 -0
- package/config/models-curated.json +4 -0
- package/config/providers.json +74 -0
- package/package.json +92 -0
- package/patches/ink+6.8.0.patch +78 -0
- package/profiles/desktop.md +65 -0
- package/profiles/generic.md +20 -0
- package/profiles/marketer.md +20 -0
- package/profiles/profiles.json +34 -0
- package/profiles/ux-reviewer.md +25 -0
- package/src/agent/agent.js +1743 -0
- package/src/agent/auto.js +346 -0
- package/src/agent/backoff.js +143 -0
- package/src/agent/compression.js +310 -0
- package/src/agent/content-resolver.js +180 -0
- package/src/agent/flow-controller.js +309 -0
- package/src/agent/intent-manifest.js +231 -0
- package/src/agent/intent-timeout.js +46 -0
- package/src/agent/intent.js +633 -0
- package/src/agent/knowledge.js +114 -0
- package/src/agent/learning.js +180 -0
- package/src/agent/modes.js +187 -0
- package/src/agent/outcome-ask.js +91 -0
- package/src/agent/project-context.js +76 -0
- package/src/agent/prompt-budget.js +117 -0
- package/src/agent/reflection-extractor.js +140 -0
- package/src/agent/steering.js +86 -0
- package/src/agent/supervisor.js +430 -0
- package/src/agent/swap.js +443 -0
- package/src/agent/system-prompt.js +446 -0
- package/src/agent/time-stamp.js +48 -0
- package/src/agent/tool-guard.js +201 -0
- package/src/agent/toolcall-text.js +162 -0
- package/src/agent/usage.js +297 -0
- package/src/agent/vision.js +94 -0
- package/src/agent/watchdog.js +139 -0
- package/src/agent/workspace-changes.js +177 -0
- package/src/api/address.js +14 -0
- package/src/api/client.js +280 -0
- package/src/api/server.js +535 -0
- package/src/api/stream-pipe.js +113 -0
- package/src/app-state.js +39 -0
- package/src/bootstrap.js +501 -0
- package/src/bus/drain-loop.js +497 -0
- package/src/bus/index.js +270 -0
- package/src/bus/plugins.js +65 -0
- package/src/child-idle.js +14 -0
- package/src/cli.js +118 -0
- package/src/commands/commands.js +1297 -0
- package/src/commands/registry.js +132 -0
- package/src/components/App.js +491 -0
- package/src/components/CarefulMenu.js +145 -0
- package/src/components/HistoryWriter.js +86 -0
- package/src/components/LineInput.js +69 -0
- package/src/components/LiveZone.js +294 -0
- package/src/components/OverlayMenu.js +179 -0
- package/src/components/SystemPanel.js +156 -0
- package/src/components/Table.js +54 -0
- package/src/config.js +249 -0
- package/src/free-models.js +230 -0
- package/src/index.js +1111 -0
- package/src/input-handler.js +13 -0
- package/src/input-text.js +123 -0
- package/src/launcher.js +129 -0
- package/src/logging/api-log.js +95 -0
- package/src/logging/chat-log-follower.js +113 -0
- package/src/logging/chat-log.js +15 -0
- package/src/logging/log-collector.js +182 -0
- package/src/logging/logger.js +112 -0
- package/src/logging/tool-log.js +20 -0
- package/src/mcp-client.js +314 -0
- package/src/memory/conversation-digest.js +113 -0
- package/src/memory/extract-facts.js +98 -0
- package/src/memory/facts.js +181 -0
- package/src/memory/inbox.js +63 -0
- package/src/memory/markdown.js +38 -0
- package/src/memory/patterns.js +185 -0
- package/src/memory/project.js +66 -0
- package/src/memory/reflections.js +74 -0
- package/src/memory/retrieval.js +84 -0
- package/src/memory/rules.js +105 -0
- package/src/memory/session-facts.js +125 -0
- package/src/memory/skills.js +191 -0
- package/src/memory/sqlite-store.js +653 -0
- package/src/memory/store.js +208 -0
- package/src/memory/tools.js +196 -0
- package/src/memory/user-model.js +86 -0
- package/src/message-handler.js +775 -0
- package/src/model-check.js +218 -0
- package/src/plugins/loader.js +120 -0
- package/src/plugins/manager.js +88 -0
- package/src/production-env.js +22 -0
- package/src/profiles.js +42 -0
- package/src/providers/adapters/anthropic.js +270 -0
- package/src/providers/adapters/openai.js +120 -0
- package/src/providers/keys-dpapi.js +41 -0
- package/src/providers/keys-fallback.js +31 -0
- package/src/providers/keys.js +132 -0
- package/src/providers/models.js +154 -0
- package/src/providers/registry.js +56 -0
- package/src/providers/state.js +56 -0
- package/src/registry.js +96 -0
- package/src/restart.js +29 -0
- package/src/sandbox/backend.js +130 -0
- package/src/security/api-auth.js +132 -0
- package/src/security/audit.js +98 -0
- package/src/security/child-policy.js +41 -0
- package/src/security/command-guard.js +173 -0
- package/src/security/content-fence.js +250 -0
- package/src/security/content-validator.js +132 -0
- package/src/security/index.js +143 -0
- package/src/security/network-guard.js +126 -0
- package/src/security/pairing.js +180 -0
- package/src/security/path-guard.js +140 -0
- package/src/security/persona-guard.js +67 -0
- package/src/security/policies.js +452 -0
- package/src/security/safety-constants.js +34 -0
- package/src/security/watchdog.js +107 -0
- package/src/sessions.js +130 -0
- package/src/spend.js +97 -0
- package/src/startup-watchdog.js +59 -0
- package/src/stdio/args.js +71 -0
- package/src/stdio/guard.js +59 -0
- package/src/stdio/protocol.js +167 -0
- package/src/stdio/run.js +106 -0
- package/src/stdio/session.js +180 -0
- package/src/store/agent-slice.js +306 -0
- package/src/store/dataset-slice.js +73 -0
- package/src/store/index.js +22 -0
- package/src/store/process-slice.js +135 -0
- package/src/store/session-slice.js +191 -0
- package/src/store/ui-slice.js +119 -0
- package/src/tasks/db.js +184 -0
- package/src/tasks/queries.js +589 -0
- package/src/tools/agent-tools.js +473 -0
- package/src/tools/checkpoint.js +152 -0
- package/src/tools/command-approvals.js +180 -0
- package/src/tools/dataset.js +50 -0
- package/src/tools/filesystem.js +682 -0
- package/src/tools/inbox-tools.js +48 -0
- package/src/tools/mesh.js +135 -0
- package/src/tools/own-env.js +136 -0
- package/src/tools/permissions.js +681 -0
- package/src/tools/plugin-tools.js +123 -0
- package/src/tools/process-tools.js +595 -0
- package/src/tools/registry.js +307 -0
- package/src/tools/swap-tools.js +72 -0
- package/src/tools/system.js +662 -0
- package/src/tools/tasks.js +532 -0
- package/src/tools/tool-search.js +171 -0
- package/src/ui/header.js +140 -0
- package/src/ui/input-cursor.js +23 -0
- package/src/ui/last-line.js +25 -0
- package/src/ui/line-edit.js +135 -0
- package/src/ui/output.js +399 -0
- package/src/ui/paste-tokens.js +131 -0
- package/src/ui/prompt-attention.js +134 -0
- package/src/ui/render-options.js +13 -0
- package/src/ui/replay.js +94 -0
- package/src/ui/splash.js +49 -0
- package/src/ui/status-level.js +36 -0
- package/src/ui/tool-ledger.js +203 -0
- package/src/ui/window-title.js +150 -0
- package/src/update.js +205 -0
- package/system.md +63 -0
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
// [a]lways on a command prompt, remembered per project.
|
|
2
|
+
//
|
|
3
|
+
// Why this is its own file and not a field on the tool permission: the answer
|
|
4
|
+
// is not "this tool is fine", it is "this command is fine, in this project".
|
|
5
|
+
// Storing it as a tool-wide `allow` (which is what the permissions file did
|
|
6
|
+
// before) opens the tool everywhere — approving `npm test` in a scratch repo
|
|
7
|
+
// would then also approve it in the one holding production credentials, and the
|
|
8
|
+
// operator's answer was never about that.
|
|
9
|
+
//
|
|
10
|
+
// The key is a normalised (project, command) pair. Three separate narrowings,
|
|
11
|
+
// each of which has a way to be got wrong:
|
|
12
|
+
//
|
|
13
|
+
// project — the grant does not travel to another checkout
|
|
14
|
+
// argv[0] — the first word only, so `npm test` covers `npm test -- --watch`
|
|
15
|
+
// and not `npm publish`. A grant is about what a command DOES.
|
|
16
|
+
// argv[0] must be an actual executable — an absolute or relative path is
|
|
17
|
+
// used as-is, because `./scripts/deploy.sh` is one program with a
|
|
18
|
+
// specific meaning and flattening it to `deploy.sh` would match a
|
|
19
|
+
// different file elsewhere on PATH.
|
|
20
|
+
//
|
|
21
|
+
// Persisted to .permissions.json under a reserved key, next to the other
|
|
22
|
+
// grants the operator has given, so there is one file to look at. A grant that
|
|
23
|
+
// lives in memory is a prompt again after a restart, which is what the owner hit
|
|
24
|
+
// on 2026-09-29 and read as "[a]lways did nothing".
|
|
25
|
+
|
|
26
|
+
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
27
|
+
import path from "node:path";
|
|
28
|
+
import { config } from "../config.js";
|
|
29
|
+
|
|
30
|
+
// config.permissionsFile, for the reason in src/config.js: under a test run
|
|
31
|
+
// the writable copy must not be the developer's own. Defensive fallback for the
|
|
32
|
+
// tests that replace the config object with only the keys they read.
|
|
33
|
+
const FILE = config.permissionsFile
|
|
34
|
+
|| path.join(config.projectRoot || process.cwd(), ".permissions.json");
|
|
35
|
+
const KEY = "_commandApprovals";
|
|
36
|
+
|
|
37
|
+
let cache = null;
|
|
38
|
+
|
|
39
|
+
function load() {
|
|
40
|
+
if (cache) return cache;
|
|
41
|
+
cache = {};
|
|
42
|
+
try {
|
|
43
|
+
if (existsSync(FILE)) {
|
|
44
|
+
const raw = JSON.parse(readFileSync(FILE, "utf8"));
|
|
45
|
+
const saved = raw[KEY];
|
|
46
|
+
if (saved && typeof saved === "object") cache = saved;
|
|
47
|
+
}
|
|
48
|
+
} catch {
|
|
49
|
+
// An unreadable or corrupt file must not take the permission system down.
|
|
50
|
+
// The failure mode is one extra prompt, which is the safe direction.
|
|
51
|
+
cache = {};
|
|
52
|
+
}
|
|
53
|
+
return cache;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function persist() {
|
|
57
|
+
try {
|
|
58
|
+
const raw = existsSync(FILE) ? JSON.parse(readFileSync(FILE, "utf8")) : {};
|
|
59
|
+
raw[KEY] = cache;
|
|
60
|
+
writeFileSync(FILE, JSON.stringify(raw, null, 2) + "\n");
|
|
61
|
+
} catch {}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* The project a command was approved in.
|
|
66
|
+
*
|
|
67
|
+
* One place decides what "the project" is, because the caller that writes a
|
|
68
|
+
* grant and the caller that reads one have to agree exactly — when they do not,
|
|
69
|
+
* an "[a]lways" silently becomes a prompt again, which is what the owner read
|
|
70
|
+
* on 2026-09-29 as "[a]lways did nothing".
|
|
71
|
+
*
|
|
72
|
+
* An explicit cwd wins, since that is the agent stating where it is working.
|
|
73
|
+
* Failing that, the session workdir, which is set once at boot and is the
|
|
74
|
+
* project the operator is sitting in. Only then the process cwd.
|
|
75
|
+
*/
|
|
76
|
+
function projectOf(cwd) {
|
|
77
|
+
const dir = cwd || process.env._FLINT_WORKDIR || process.cwd() || config.projectRoot;
|
|
78
|
+
return path.normalize(path.resolve(dir));
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* What the grant is about: the program being run, and its subcommand.
|
|
83
|
+
*
|
|
84
|
+
* The first two words, not the whole command line and not just the first word.
|
|
85
|
+
* Both extremes are wrong and both were tried:
|
|
86
|
+
*
|
|
87
|
+
* argv[0] only — a grant for `npm test` then covered `npm publish`, which is
|
|
88
|
+
* the opposite of what the operator agreed to. This is what
|
|
89
|
+
* the first version of this file did, and the test that
|
|
90
|
+
* caught it is the reason the subcommand is here.
|
|
91
|
+
* whole line — approving `npm test` does not then cover `npm test -- --watch`,
|
|
92
|
+
* so the operator answers the same question again over a flag.
|
|
93
|
+
*
|
|
94
|
+
* A leading path is the whole program: `./scripts/deploy.sh` is one specific
|
|
95
|
+
* file, and taking its next word as a subcommand would key the grant on
|
|
96
|
+
* whatever that script happens to be called.
|
|
97
|
+
*
|
|
98
|
+
* Known coarseness, stated rather than hidden: the second word is taken
|
|
99
|
+
* literally, so a grant for `git -C` covers other `git -C` invocations, and one
|
|
100
|
+
* for `npm` alone covers only bare `npm`. Both are over-grants in a narrow
|
|
101
|
+
* corner, and the alternative — parsing every tool's flag grammar — is a worse
|
|
102
|
+
* trade than a prompt that occasionally reappears.
|
|
103
|
+
*
|
|
104
|
+
* @returns {string|null} null for an empty command, which is "no grant applies"
|
|
105
|
+
*/
|
|
106
|
+
export function commandProgram(command) {
|
|
107
|
+
if (!command || typeof command !== "string") return null;
|
|
108
|
+
const trimmed = command.trim();
|
|
109
|
+
if (!trimmed) return null;
|
|
110
|
+
const m = trimmed.match(/^"([^"]+)"|^'([^']+)'|^(\S+)/);
|
|
111
|
+
if (!m) return null;
|
|
112
|
+
const program = m[1] || m[2] || m[3] || null;
|
|
113
|
+
if (!program) return null;
|
|
114
|
+
// A path is the whole program — see above.
|
|
115
|
+
if (/[\\/]/.test(program)) return program;
|
|
116
|
+
const sub = trimmed.slice(m[0].length).trim().match(/^(\S+)/);
|
|
117
|
+
return sub ? `${program} ${sub[1]}` : program;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** The key a grant is stored under. */
|
|
121
|
+
export function approvalKey({ cwd, command } = {}) {
|
|
122
|
+
const program = commandProgram(command);
|
|
123
|
+
if (!program) return null;
|
|
124
|
+
return `${projectOf(cwd)}\u0000${program}`;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Has this command already been approved in this project?
|
|
129
|
+
*
|
|
130
|
+
* Only the program is compared, which is why the grant is deliberately coarse:
|
|
131
|
+
* the alternative is storing whole command lines, where approving one invocation
|
|
132
|
+
* does not generalise and the operator answers the same question again on the
|
|
133
|
+
* next flag change. The subcommand is kept on purpose — `npm test` and
|
|
134
|
+
* `npm publish` are different decisions — while flags are not, because they
|
|
135
|
+
* select behaviour within one decision and are the thing the operator does not
|
|
136
|
+
* want to re-approve.
|
|
137
|
+
*/
|
|
138
|
+
export function getCommandApproval({ cwd, command } = {}) {
|
|
139
|
+
const key = approvalKey({ cwd, command });
|
|
140
|
+
if (!key) return false;
|
|
141
|
+
return Boolean(load()[key]);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Remember an approval for this command in this project.
|
|
146
|
+
*
|
|
147
|
+
* @returns {string|null} the key stored, or null if there was nothing to store
|
|
148
|
+
*/
|
|
149
|
+
export function grantCommandApproval({ cwd, command } = {}) {
|
|
150
|
+
const key = approvalKey({ cwd, command });
|
|
151
|
+
if (!key) return null;
|
|
152
|
+
load()[key] = true;
|
|
153
|
+
persist();
|
|
154
|
+
return key;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/** Forget one approval. Used by /permissions and by tests. */
|
|
158
|
+
export function revokeCommandApproval({ cwd, command } = {}) {
|
|
159
|
+
const key = approvalKey({ cwd, command });
|
|
160
|
+
if (!key || !load()[key]) return false;
|
|
161
|
+
delete cache[key];
|
|
162
|
+
persist();
|
|
163
|
+
return true;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/** Every grant, for display. */
|
|
167
|
+
export function listCommandApprovals() {
|
|
168
|
+
return { ...load() };
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Drop the in-memory copy, so the next read comes from disk.
|
|
173
|
+
*
|
|
174
|
+
* Exported for tests, which need to prove an approval survived a restart. A
|
|
175
|
+
* test that could not simulate a restart cannot check that it survived one, and
|
|
176
|
+
* that is the property that was broken.
|
|
177
|
+
*/
|
|
178
|
+
export function _resetForTest() {
|
|
179
|
+
cache = null;
|
|
180
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// Dataset navigation tool — lets the model page through stored datasets
|
|
2
|
+
|
|
3
|
+
export const datasetToolDefs = [
|
|
4
|
+
{
|
|
5
|
+
type: "function",
|
|
6
|
+
function: {
|
|
7
|
+
name: "show_dataset",
|
|
8
|
+
description: "Navigate to a specific page of a named dataset. Datasets are created when tools return large result sets. Check 'Active datasets' in your context for available IDs.",
|
|
9
|
+
parameters: {
|
|
10
|
+
type: "object",
|
|
11
|
+
properties: {
|
|
12
|
+
id: { type: "string", description: "Dataset ID (e.g. ds_1)" },
|
|
13
|
+
page: { type: "number", description: "Page number (1-based). Omit to go to next page." },
|
|
14
|
+
},
|
|
15
|
+
required: ["id"],
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
export function createDatasetHandlers(store) {
|
|
22
|
+
return {
|
|
23
|
+
show_dataset({ id, page }) {
|
|
24
|
+
const ds = store.getState().datasets[id];
|
|
25
|
+
if (!ds) {
|
|
26
|
+
const available = Object.values(store.getState().datasets)
|
|
27
|
+
.map((d) => `${d.id} "${d.label}"`)
|
|
28
|
+
.join(", ");
|
|
29
|
+
return `Error: dataset "${id}" not found. Available: ${available || "none"}`;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const totalPages = Math.ceil(ds.rows.length / ds.pageSize);
|
|
33
|
+
const target = page || ds.page + 1;
|
|
34
|
+
const clamped = Math.max(1, Math.min(target, totalPages));
|
|
35
|
+
store.getState().setDatasetPage(id, clamped);
|
|
36
|
+
|
|
37
|
+
const start = (clamped - 1) * ds.pageSize;
|
|
38
|
+
const pageRows = ds.rows.slice(start, start + ds.pageSize);
|
|
39
|
+
|
|
40
|
+
return {
|
|
41
|
+
_table: true,
|
|
42
|
+
_pagination: true, // skip re-registration as new dataset
|
|
43
|
+
columns: ds.columns,
|
|
44
|
+
rows: pageRows,
|
|
45
|
+
title: `${ds.label} (page ${clamped}/${totalPages}, ${ds.rows.length} total)`,
|
|
46
|
+
name: ds.label,
|
|
47
|
+
};
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
}
|