@piagent/platform 1.4.0 → 1.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +93 -0
- package/README.md +8 -2
- package/README.vi.md +27 -0
- package/docs/command-reference-vietnamese.md +2 -2
- package/docs/distribution-standard.md +7 -7
- package/docs/en/architecture.md +13 -0
- package/docs/languages.json +20 -4
- package/docs/mcp-and-tools.md +112 -1
- package/docs/operator-manual-vietnamese.md +7 -7
- package/docs/publishing-for-teams.md +36 -0
- package/docs/release-install-policy.md +21 -8
- package/docs/runtime-harness-standard.md +1 -1
- package/docs/security-threat-model.md +1 -1
- package/docs/task-implementation-contract.md +11 -4
- package/docs/team-onboarding.md +2 -2
- package/docs/usage-observability.md +70 -0
- package/docs/vi/architecture.md +13 -0
- package/docs-site/architecture.html +5 -1
- package/docs-site/benchmark.html +5 -1
- package/docs-site/commands.html +5 -1
- package/docs-site/content/en/index.html +49 -0
- package/docs-site/content/en/runtime.html +13 -0
- package/docs-site/content/index.html +49 -0
- package/docs-site/content/runtime.html +28 -0
- package/docs-site/context-engine.html +5 -1
- package/docs-site/en/architecture.html +5 -1
- package/docs-site/en/benchmark.html +5 -1
- package/docs-site/en/commands.html +5 -1
- package/docs-site/en/context-engine.html +5 -1
- package/docs-site/en/index.html +54 -1
- package/docs-site/en/mcp-approval.html +5 -1
- package/docs-site/en/mcp-auth.html +5 -1
- package/docs-site/en/mcp-servers.html +5 -1
- package/docs-site/en/mcp.html +5 -1
- package/docs-site/en/permissions.html +5 -1
- package/docs-site/en/profiles.html +5 -1
- package/docs-site/en/quickstart.html +5 -1
- package/docs-site/en/runtime.html +18 -1
- package/docs-site/en/scope.html +5 -1
- package/docs-site/en/security.html +5 -1
- package/docs-site/en/team.html +5 -1
- package/docs-site/en/whats-new.html +5 -1
- package/docs-site/en/workflows.html +5 -1
- package/docs-site/index.html +54 -1
- package/docs-site/mcp-approval.html +5 -1
- package/docs-site/mcp-auth.html +5 -1
- package/docs-site/mcp-servers.html +5 -1
- package/docs-site/mcp.html +5 -1
- package/docs-site/permissions.html +5 -1
- package/docs-site/profiles.html +5 -1
- package/docs-site/quickstart.html +5 -1
- package/docs-site/runtime.html +33 -1
- package/docs-site/scope.html +5 -1
- package/docs-site/security.html +5 -1
- package/docs-site/team.html +5 -1
- package/docs-site/whats-new.html +5 -1
- package/docs-site/workflows.html +5 -1
- package/package.json +14 -2
- package/packages/piagent-core/README.md +3 -2
- package/packages/piagent-core/capabilities/capability-verification-cache.js +17 -7
- package/packages/piagent-core/capabilities/runtime-integrity.js +51 -4
- package/packages/piagent-core/extensions/piagent-guard.ts +18 -235
- package/packages/piagent-core/extensions/runtime-evidence.js +12 -1
- package/packages/piagent-core/extensions/shell-reach.ts +250 -0
- package/packages/piagent-core/mcp/mcp-command-actions.js +1 -1
- package/packages/piagent-core/mcp/mcp-server-catalog.js +135 -5
- package/packages/piagent-core/package.json +1 -1
- package/packages/piagent-core/runtime/hooks/tool-call-hook.ts +9 -2
- package/packages/piagent-core/runtime/orchestration/owned-work-budget.ts +25 -2
- package/packages/piagent-core/runtime/policy/block-remedy.ts +139 -0
- package/packages/piagent-core/runtime/registration/task-start-tool.ts +9 -13
- package/packages/piagent-core/runtime/session/token-economics.js +34 -0
- package/packages/piagent-core/runtime/tools/phase-tool-runtime.ts +2 -1
- package/packages/piagent-core/runtime/workflows/task-intake.ts +5 -0
- package/packages/piagent-core/security/sensitive-data.js +7 -0
- package/packages/piagent-webui/dist/client/assets/index-bu7XpD9Q.js +172 -0
- package/packages/piagent-webui/dist/client/index.html +1 -1
- package/packages/piagent-webui/extension/lifecycle-controller.ts +15 -0
- package/packages/piagent-webui/extension/piagent-webui.ts +4 -4
- package/packages/piagent-webui/extension/same-session-bridge.ts +3 -3
- package/packages/piagent-webui/gateway/extension-authority.ts +47 -0
- package/packages/piagent-webui/gateway/session-inspection-registry.ts +37 -2
- package/packages/piagent-webui/gateway/session-runtime-supervisor.ts +5 -1
- package/packages/piagent-webui/server/core-inspection-provider.ts +5 -5
- package/packages/piagent-webui/server/transcript-projection.ts +15 -0
- package/scripts/build-docs-site.mjs +21 -1
- package/scripts/explain-command.mjs +240 -0
- package/scripts/install-global.sh +104 -3
- package/scripts/mcp-manage.mjs +17 -4
- package/scripts/pi-session-stats.sh +10 -2
- package/scripts/piagent-cli.mjs +14 -1
- package/scripts/piagent-dashboard.mjs +55 -2
- package/scripts/setup.sh +69 -33
- package/scripts/traffic-snapshot.mjs +214 -0
- package/scripts/typescript-loader.mjs +20 -4
- package/scripts/uninstall-global.sh +53 -2
- package/scripts/update-global.mjs +33 -1
- package/scripts/verify-local.sh +4 -0
- package/scripts/verify-translation-pairing.mjs +121 -0
- package/packages/piagent-webui/dist/client/assets/index-DjMntaK5.js +0 -172
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
// What to do about a refusal.
|
|
2
|
+
//
|
|
3
|
+
// A blocked tool call is the only conversation most operators ever have with
|
|
4
|
+
// this policy, and for most of them it was one-sided: the guard states 74
|
|
5
|
+
// distinct refusals and three of them said what to do next. The rest ended at
|
|
6
|
+
// "no", which leaves the reader guessing at a rule they cannot see.
|
|
7
|
+
//
|
|
8
|
+
// Remedies are attached in one place rather than at the 29 sites that build a
|
|
9
|
+
// refusal, for the reason this codebase keeps arriving at: a rule applied at
|
|
10
|
+
// every call site is a rule that will be missed at the next one. Every decision
|
|
11
|
+
// leaves through `registerToolCallHook`, so it is enough to answer there, and a
|
|
12
|
+
// refusal added tomorrow is answered without anyone remembering to.
|
|
13
|
+
|
|
14
|
+
// Matched against the refusal text in order; the first match wins, so put the
|
|
15
|
+
// narrower phrase above the broader one it would otherwise be swallowed by.
|
|
16
|
+
const REMEDIES: Array<{ match: RegExp; remedy: string }> = [
|
|
17
|
+
{
|
|
18
|
+
// `grep glob targeting` and `find pattern targeting` are the same refusal
|
|
19
|
+
// reached through a different tool, and matching only the shell wording left
|
|
20
|
+
// two thirds of this family answering nothing.
|
|
21
|
+
match: /glob can target protected path|glob targeting protected path|pattern targeting protected path/i,
|
|
22
|
+
remedy: "Name the exact file instead of a pattern: a pattern is refused when any name it can match is protected."
|
|
23
|
+
+ " `piagent explain '<command>'` shows which name it matched."
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
match: /builds a filename this guard cannot resolve/i,
|
|
27
|
+
// This refusal already carries its own guidance, so it only gains the
|
|
28
|
+
// pointer to the tool that shows the whole chain.
|
|
29
|
+
remedy: "`piagent explain '<command>'` shows every step that ran and what each one saw."
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
match: /(touches|resolves to) protected path|access to protected path|write to protected path/i,
|
|
33
|
+
remedy: "Use an approved `piagent-context` projection if one is available. Direct access requires the operator"
|
|
34
|
+
+ " to change the protected-path policy; task scope alone does not override it."
|
|
35
|
+
+ " `piagent explain '<command>'` shows which pattern matched."
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
match: /write to read-only path/i,
|
|
39
|
+
remedy: "The active profile marks this path read-only. Change `readOnlyPaths` in `.pi/piagent-profile.json`"
|
|
40
|
+
+ " if the project really should allow writes here, then reapply the profile."
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
match: /outside resolved filesystem scope/i,
|
|
44
|
+
remedy: "Work inside the project, or widen the filesystem scope in the active profile and reapply it."
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
match: /cannot mutate paths outside its declared scope|semantic repair must stay inside its declared task scope/i,
|
|
48
|
+
remedy: "Start a new attempt with `piagent_task_start` listing this path in the scope; a running task cannot widen its own."
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
match: /Task Implementation Contract is required/i,
|
|
52
|
+
remedy: "Call `piagent_task_start` once with explicit project-relative scope, then retry."
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
match: /Capability lock is (missing|unreadable)|does not match what this project agreed to/i,
|
|
56
|
+
remedy: "Reapply the project profile to rebuild the lock: `piagent-init <project> --force-profile`."
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
match: /runtime changed during this session/i,
|
|
60
|
+
remedy: "Restart the Pi session. The lock is re-verified and re-pinned at session start, never mid-session."
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
match: /Permission profile read-only blocked/i,
|
|
64
|
+
remedy: "This session is read-only. Change the access level in Settings, or start it with a writable permission profile."
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
match: /Tool (is not registered|registry is disabled|registry blocked)/i,
|
|
68
|
+
remedy: "Enable the tool group for this profile, or use a tool the profile already allows."
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
match: /lifecycle control blocks tool start|resume is blocked|could not enter an audited repair phase/i,
|
|
72
|
+
remedy: "Resolve the task lifecycle first — resume, repair, or close the current attempt — then retry."
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
match: /path traverses symbolic link|path resolves outside the project|path component cannot be inspected/i,
|
|
76
|
+
remedy: "Use the real path inside the project. A link that leaves the project is refused rather than followed."
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
match: /requires confirmation|requires explicit operator approval/i,
|
|
80
|
+
remedy: "Approve it in the session when prompted; this is a decision the operator makes, not a defect."
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
// The whole session is refused here, so this is the refusal most in need of
|
|
84
|
+
// a next step -- and the one the narrower MCP wording used to miss.
|
|
85
|
+
match: /Blocked every tool call/i,
|
|
86
|
+
remedy: "Every tool call is refused while a repository config can reach an ungated server."
|
|
87
|
+
+ " Run `piagent-mcp doctor` to see which config stopped the session, then approve or remove those servers."
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
match: /MCP (proxy|shell carrier|command|server)/i,
|
|
91
|
+
remedy: "Check the server with `piagent-mcp doctor`, and approve it explicitly if the project should reach it."
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
match: /is read-only; .*cannot mutate|read-only inspection allowlist/i,
|
|
95
|
+
remedy: "This task was started read-only. Start a source-change task with `piagent_task_start` when the work needs to write."
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
match: /opaque shell mutation whose write target is not statically bounded/i,
|
|
99
|
+
remedy: "Use the edit, write, or apply-patch tools so the target is named, instead of a shell command that computes it."
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
match: /Context budget blocked editing large file/i,
|
|
103
|
+
remedy: "Edit a narrower range of the file, or split the change, so the edit stays inside the context budget."
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
match: /Approval became stale/i,
|
|
107
|
+
remedy: "Ask for the action again; an approval is bound to the state it was granted for and does not survive a change to it."
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
match: /Missing capability|Capability validation failed/i,
|
|
111
|
+
remedy: "Add the pack that provides it to the project profile, then reapply the profile."
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
match: /nesting exceeds inspection depth|unsafe scope pattern/i,
|
|
115
|
+
remedy: "Flatten the tool input or narrow the scope pattern: anything the guard cannot finish inspecting is refused rather than guessed at."
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
match: /start a new attempt or a fresh task/i,
|
|
119
|
+
remedy: "Start the next attempt with `piagent_task_start`; a closed attempt cannot keep mutating the project."
|
|
120
|
+
}
|
|
121
|
+
];
|
|
122
|
+
|
|
123
|
+
// Kept out of the appended text when the refusal already ends by saying what to
|
|
124
|
+
// do, so a reader is never given the same instruction twice in one line.
|
|
125
|
+
const ALREADY_GUIDED = /Write the path out|Reapply the project profile|Restart the session|then retry\.$|Start the task first/i;
|
|
126
|
+
|
|
127
|
+
export function remedyForReason(reason: string): string | undefined {
|
|
128
|
+
if (typeof reason !== "string" || reason.trim().length === 0) return undefined;
|
|
129
|
+
const found = REMEDIES.find((entry) => entry.match.test(reason));
|
|
130
|
+
if (!found) return undefined;
|
|
131
|
+
if (ALREADY_GUIDED.test(reason) && !/glob can target|cannot resolve/i.test(reason)) return undefined;
|
|
132
|
+
return found.remedy;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export function withRemedy<T extends { block: true; reason: string }>(decision: T): T {
|
|
136
|
+
const remedy = remedyForReason(decision.reason);
|
|
137
|
+
if (!remedy || decision.reason.includes(remedy)) return decision;
|
|
138
|
+
return { ...decision, reason: `${decision.reason.replace(/\s+$/, "")} → ${remedy}` };
|
|
139
|
+
}
|
|
@@ -44,10 +44,11 @@ export function registerTaskStartTool(pi: ExtensionAPI, deps: Record<string, any
|
|
|
44
44
|
const taskStartTool = {
|
|
45
45
|
name: "piagent_task_start",
|
|
46
46
|
label: "Piagent Task Start",
|
|
47
|
-
description: "Create a Task Implementation Contract
|
|
48
|
-
promptSnippet: "Start a governed
|
|
47
|
+
description: "Create a Task Implementation Contract before governed project inspection, command execution, or source changes.",
|
|
48
|
+
promptSnippet: "Start a governed project task and persist the task contract.",
|
|
49
49
|
promptGuidelines: [
|
|
50
50
|
"Call this exactly once before source edits in a project managed by Pi Agent Platform.",
|
|
51
|
+
"Use source-change for project command execution, including tests, builds, lint, typecheck, verification, and package dry-runs, even when the operator requires source files to remain unchanged. Read-only intentionally blocks shell execution.",
|
|
51
52
|
"Do not call context, status, policy, evidence-recording, trace, or gate tools first; runtime hooks provide those checks automatically.",
|
|
52
53
|
"Use tiny for a bounded low-risk change, normal for ordinary multi-file work, and high-risk for security, data, release, migration, or external-impact work.",
|
|
53
54
|
"Every scope entry must be a project-relative path or glob such as src/file.ts, src/**, or test/**; never put prose in scope.",
|
|
@@ -58,7 +59,9 @@ export function registerTaskStartTool(pi: ExtensionAPI, deps: Record<string, any
|
|
|
58
59
|
taskId: Type.Optional(Type.String({ minLength: 1 })),
|
|
59
60
|
summary: Type.String({ minLength: 10 }),
|
|
60
61
|
riskLane: StringEnum(["tiny", "normal", "high-risk"] as const),
|
|
61
|
-
changeMode: Type.Optional(StringEnum(["source-change", "read-only"] as const
|
|
62
|
+
changeMode: Type.Optional(StringEnum(["source-change", "read-only"] as const, {
|
|
63
|
+
description: "Use source-change for project command execution as well as edits; use read-only only for inspection that needs no shell execution."
|
|
64
|
+
})),
|
|
62
65
|
maxAttempts: Type.Optional(Type.Number({ minimum: 1, maximum: 10 })),
|
|
63
66
|
expectedOutput: Type.String({ minLength: 10 }),
|
|
64
67
|
acceptanceCriteria: Type.Array(Type.String({ minLength: 1 }), { minItems: 1, maxItems: 12 }),
|
|
@@ -103,15 +106,8 @@ export function registerTaskStartTool(pi: ExtensionAPI, deps: Record<string, any
|
|
|
103
106
|
const migrationRetry = active?.taskId === taskId
|
|
104
107
|
&& active?.workingTreeDigestMigration?.status === "new-attempt-required";
|
|
105
108
|
const authorityRetry = active?.taskId === taskId && activeAuthorityReplacement?.required === true;
|
|
106
|
-
if (active && !migrationRetry && !authorityRetry) {
|
|
109
|
+
if (active?.trace.outcome === "pending" && !migrationRetry && !authorityRetry) {
|
|
107
110
|
if (active.taskId === taskId) {
|
|
108
|
-
if (active.trace.outcome !== "pending") {
|
|
109
|
-
return {
|
|
110
|
-
content: [{ type: "text", text: `Session already belongs to terminal task ${active.taskId} (${active.trace.outcome}). Start a fresh Pi session for a retry or new task.` }],
|
|
111
|
-
details: active,
|
|
112
|
-
isError: true
|
|
113
|
-
};
|
|
114
|
-
}
|
|
115
111
|
const manifest = repositoryFileManifest(ctx.cwd);
|
|
116
112
|
const currentScopeResolution = resolveTaskScopePatterns(active.scope, manifest);
|
|
117
113
|
const proposedScopeResolution = Array.isArray(params.scope)
|
|
@@ -151,7 +147,7 @@ export function registerTaskStartTool(pi: ExtensionAPI, deps: Record<string, any
|
|
|
151
147
|
};
|
|
152
148
|
}
|
|
153
149
|
return {
|
|
154
|
-
content: [{ type: "text", text: `Session already
|
|
150
|
+
content: [{ type: "text", text: `Session already has pending task ${active.taskId} (${active.taskRunId}). Complete or stop it before starting another task in this conversation.` }],
|
|
155
151
|
details: active,
|
|
156
152
|
isError: true
|
|
157
153
|
};
|
|
@@ -428,7 +424,7 @@ export function registerTaskStartTool(pi: ExtensionAPI, deps: Record<string, any
|
|
|
428
424
|
const started = await taskStartTool.execute(
|
|
429
425
|
`runtime-intake-${ctx.sessionManager.getSessionId()}`,
|
|
430
426
|
{
|
|
431
|
-
taskId: hasOperatorSessionName(sessionName) ? sessionName : summary,
|
|
427
|
+
taskId: active && active.trace.outcome !== "pending" ? summary : hasOperatorSessionName(sessionName) ? sessionName : summary,
|
|
432
428
|
summary,
|
|
433
429
|
riskLane: automaticTaskRiskLane(prompt),
|
|
434
430
|
intakeMode: "runtime",
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// What a session's token counts mean, in one place.
|
|
2
|
+
//
|
|
3
|
+
// `fresh` is the benchmark's own definition -- input + output, the tokens billed
|
|
4
|
+
// at full rate -- restated here rather than recomputed differently by each
|
|
5
|
+
// reader. The published benchmark and the terminal must never mean two things by
|
|
6
|
+
// the same word.
|
|
7
|
+
//
|
|
8
|
+
// The cache hit rate is reported beside `fresh`, never instead of it. In that
|
|
9
|
+
// same benchmark the baseline surface held the *higher* hit rate, 84.9% against
|
|
10
|
+
// 53.0%, while spending more than twice the fresh tokens: a high rate on a large
|
|
11
|
+
// prompt is not a cheap session. The rate explains a cost, it does not report one.
|
|
12
|
+
|
|
13
|
+
const finite = (value) => typeof value === "number" && Number.isFinite(value);
|
|
14
|
+
|
|
15
|
+
export function sessionTokenEconomics(tokens = {}) {
|
|
16
|
+
const { input, output, cacheRead } = tokens ?? {};
|
|
17
|
+
const fresh = finite(input) && finite(output) ? input + output : null;
|
|
18
|
+
const prompt = finite(cacheRead) && finite(input) ? cacheRead + input : null;
|
|
19
|
+
return {
|
|
20
|
+
fresh,
|
|
21
|
+
// A session that has read nothing has no rate to report, which is not the
|
|
22
|
+
// same as a rate of zero.
|
|
23
|
+
cacheHitRate: prompt !== null && prompt > 0 ? cacheRead / prompt : null
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function formatTokenEconomics(economics, formatNumber, formatPercent) {
|
|
28
|
+
return {
|
|
29
|
+
fresh: economics.fresh === null ? "unknown" : `${formatNumber(economics.fresh)} billed at full rate`,
|
|
30
|
+
cache: economics.cacheHitRate === null
|
|
31
|
+
? "unknown"
|
|
32
|
+
: `${formatPercent(100 * economics.cacheHitRate)} of prompt tokens served from cache`
|
|
33
|
+
};
|
|
34
|
+
}
|
|
@@ -80,7 +80,8 @@ export class PhaseToolRuntime {
|
|
|
80
80
|
|
|
81
81
|
toolDecision(ctx: ExtensionContext, toolName: string): { block: true; reason: string } | undefined {
|
|
82
82
|
if (this.effectiveMode(ctx) !== "on") return undefined;
|
|
83
|
-
if (toolName === "piagent_task_start"
|
|
83
|
+
if (toolName === "piagent_task_start"
|
|
84
|
+
&& (this.digestMigrationStatus(ctx) === "new-attempt-required" || this.states.get(this.key(ctx))?.currentPhase === "terminal")) return undefined;
|
|
84
85
|
const state = this.states.get(this.key(ctx));
|
|
85
86
|
if (!state) return undefined;
|
|
86
87
|
const policy = phaseToolPolicy(state.currentPhase, state.changeMode);
|
|
@@ -12,6 +12,7 @@ const AUTO_INTAKE_READ_ONLY_LEAD = /^\s*\/?(?:analy[sz]e|audit|check|discuss|exp
|
|
|
12
12
|
const AUTO_INTAKE_MANUAL_RISK = /\b(?:credential|database|deploy|destructive|encryption|external provider|payment|permission|production|publish|secret|token rotation)\b/i;
|
|
13
13
|
const AUTO_READ_ONLY_INTENT = /\b(?:analy[sz]e|audit|check|diagnos(?:e|is)|explain|inspect|investigate|plan|research|review|scout|summari[sz]e|triage|kiem tra|nghien cuu|giai thich|danh gia)\b/i;
|
|
14
14
|
const AUTO_READ_ONLY_BOUNDARY = /\b(?:read-only|no edits?|do not edit(?: files?| source| project| repo)?|do not change (?:files?|source|project|repo)|do not mutate (?:files?|source|project|repo|workspace)|khong sua(?: file| source| project)?|khong edit(?: file| source| project)?|khong doi(?: file| source| project)?)\b/i;
|
|
15
|
+
const AUTO_EXECUTION_INTENT = /(?:\b(?:run|execute|execution|rerun|re-run|chay)\b.{0,80}\b(?:tests?|build|checks?|gates?|lint|typecheck|package|pack|verify|verification)\b|\b(?:npm|pnpm|yarn|bun)\s+(?:test|pack|run\s+(?:build|check|lint|typecheck|verify))\b)/i;
|
|
15
16
|
|
|
16
17
|
export const AUTO_INTAKE_SNAPSHOT_PATTERNS = [
|
|
17
18
|
"src/**", "app/**", "lib/**", "packages/**", "test/**", "tests/**", "spec/**", "__tests__/**"
|
|
@@ -49,6 +50,10 @@ export function automaticTaskIntakeEligible(prompt: string, readProtectedPaths:
|
|
|
49
50
|
const text = String(prompt ?? "").trim();
|
|
50
51
|
if (!text || text.length > AUTO_INTAKE_MAX_PROMPT_CHARS) return false;
|
|
51
52
|
const signal = classifyContextTask(text);
|
|
53
|
+
if (AUTO_EXECUTION_INTENT.test(text)) {
|
|
54
|
+
if (/\bpiagent_task_start\b/i.test(text)) return false;
|
|
55
|
+
return !signal.paths.some((candidate) => matchesProtectedPath(candidate, readProtectedPaths));
|
|
56
|
+
}
|
|
52
57
|
if (signal.workflow !== "task" || !AUTO_INTAKE_CHANGE_INTENT.test(text)) return false;
|
|
53
58
|
if (AUTO_INTAKE_READ_ONLY_LEAD.test(text) || AUTO_INTAKE_MANUAL_RISK.test(text)) return false;
|
|
54
59
|
if (/\bpiagent_task_start\b/i.test(text)) return false;
|
|
@@ -8,6 +8,13 @@ const TOKEN_PATTERNS = [
|
|
|
8
8
|
/\bsk-(?:(?:proj|svcacct|admin|ant)-)?[A-Za-z0-9_-]{20,}\b/g,
|
|
9
9
|
/\bsk_(?:live|test)_[A-Za-z0-9]{16,}\b/g,
|
|
10
10
|
/\bxox[baprs]-[A-Za-z0-9-]{10,}\b/g,
|
|
11
|
+
// An npm token was recognised only where something named it -- `NPM_TOKEN=...`
|
|
12
|
+
// caught the assignment, a field called `token` caught the field -- so the one
|
|
13
|
+
// place it actually appears was missed: `//registry.npmjs.org/:_authToken=npm_…`
|
|
14
|
+
// is the whole of an `.npmrc`, and a `cat` of that file or a failing publish log
|
|
15
|
+
// carried the credential through unredacted. Every other provider here is
|
|
16
|
+
// recognised by the shape of its token; npm was the gap.
|
|
17
|
+
/\bnpm_[A-Za-z0-9]{30,}\b/g,
|
|
11
18
|
/\bAIza[0-9A-Za-z_-]{20,}\b/g,
|
|
12
19
|
/\beyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\b/g
|
|
13
20
|
];
|