fraim-hub 2.0.274 → 2.0.276
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/dist/src/ai-hub/catalog.js +26 -5
- package/dist/src/ai-hub/configured-agent-command.js +152 -0
- package/dist/src/ai-hub/configured-agents.js +65 -13
- package/dist/src/ai-hub/desktop-main.js +1 -1
- package/dist/src/ai-hub/hub-latest-version.js +35 -6
- package/dist/src/ai-hub/office-sideload.js +12 -3
- package/dist/src/ai-hub/server.js +147 -44
- package/dist/src/cli/setup/ide-global-integration.js +24 -15
- package/dist/src/config/persona-capability-bundles.js +3 -1
- package/extensions/office-word/taskpane.html +20 -11
- package/package.json +2 -2
- package/public/ai-hub/excel-taskpane/icon-64.png +0 -0
- package/public/ai-hub/excel-taskpane/index.html +298 -0
- package/public/ai-hub/excel-taskpane/manifest.xml +33 -0
- package/public/ai-hub/script.js +184 -76
- package/public/ai-hub/styles.css +22 -5
|
@@ -379,6 +379,27 @@ function readJobFrontmatter(filePath) {
|
|
|
379
379
|
return null;
|
|
380
380
|
}
|
|
381
381
|
}
|
|
382
|
+
/**
|
|
383
|
+
* Issue #1246: resolve the stub whose phase graph actually governs this job.
|
|
384
|
+
* A personalized override that is `extends`-only (no `phases`/`initialPhase`
|
|
385
|
+
* of its own) wins the layer-precedence override chain in findJobStubPath,
|
|
386
|
+
* but declares no phase graph AND has no `## Steps` section for
|
|
387
|
+
* loadJobPhasesFromSteps to fall back to either — every phase-derivation
|
|
388
|
+
* caller silently degraded to zero phases. Follow `extends` to the baseline
|
|
389
|
+
* stub when the resolved stub itself declares no phase graph.
|
|
390
|
+
*/
|
|
391
|
+
function resolveDeclaredPhaseStub(stubPath, projectPath) {
|
|
392
|
+
const fm = readJobFrontmatter(stubPath);
|
|
393
|
+
if (fm?.initialPhase && fm.phases)
|
|
394
|
+
return { fm, path: stubPath };
|
|
395
|
+
const extendsValue = typeof fm?.extends === 'string' ? fm.extends.trim() : '';
|
|
396
|
+
if (!extendsValue)
|
|
397
|
+
return { fm, path: stubPath };
|
|
398
|
+
const basePath = resolveExtendedStubPath(projectPath, extendsValue);
|
|
399
|
+
if (!basePath)
|
|
400
|
+
return { fm, path: stubPath };
|
|
401
|
+
return { fm: readJobFrontmatter(basePath), path: basePath };
|
|
402
|
+
}
|
|
382
403
|
function findJobStubPath(projectPath, jobId) {
|
|
383
404
|
// Walk both employee and manager layer sets. The analytics surfaces can
|
|
384
405
|
// inspect any FRAIM job run, including manager-side jobs such as
|
|
@@ -432,9 +453,9 @@ function loadJobPhases(jobId, projectPath, discriminant = 'feature') {
|
|
|
432
453
|
const stubPath = findJobStubPath(projectPath, jobId);
|
|
433
454
|
if (!stubPath)
|
|
434
455
|
return [];
|
|
435
|
-
const fm =
|
|
456
|
+
const { fm, path: declaredPath } = resolveDeclaredPhaseStub(stubPath, projectPath);
|
|
436
457
|
if (!fm || !fm.initialPhase || !fm.phases)
|
|
437
|
-
return loadJobPhasesFromSteps(
|
|
458
|
+
return loadJobPhasesFromSteps(declaredPath);
|
|
438
459
|
const visited = new Set();
|
|
439
460
|
const ordered = [];
|
|
440
461
|
let cursor = fm.initialPhase;
|
|
@@ -461,7 +482,7 @@ function resolveJobPhaseTransition(jobId, projectPath, phaseId, outcome, discrim
|
|
|
461
482
|
const stubPath = findJobStubPath(projectPath, jobId);
|
|
462
483
|
if (!stubPath)
|
|
463
484
|
return null;
|
|
464
|
-
const fm =
|
|
485
|
+
const { fm } = resolveDeclaredPhaseStub(stubPath, projectPath);
|
|
465
486
|
if (!fm || !fm.phases)
|
|
466
487
|
return null;
|
|
467
488
|
const phaseDef = fm.phases[phaseId];
|
|
@@ -474,9 +495,9 @@ function loadAllJobPhaseIds(jobId, projectPath) {
|
|
|
474
495
|
const stubPath = findJobStubPath(projectPath, jobId);
|
|
475
496
|
if (!stubPath)
|
|
476
497
|
return new Set();
|
|
477
|
-
const fm =
|
|
498
|
+
const { fm, path: declaredPath } = resolveDeclaredPhaseStub(stubPath, projectPath);
|
|
478
499
|
if (!fm || !fm.phases) {
|
|
479
|
-
const phases = loadJobPhasesFromSteps(
|
|
500
|
+
const phases = loadJobPhasesFromSteps(declaredPath);
|
|
480
501
|
return new Set(phases.map((p) => p.id));
|
|
481
502
|
}
|
|
482
503
|
return new Set(Object.keys(fm.phases));
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.parseConfiguredCommand = parseConfiguredCommand;
|
|
7
|
+
exports.describeConfiguredCommandInvocationProblem = describeConfiguredCommandInvocationProblem;
|
|
8
|
+
exports.commandContainsLiteralSecret = commandContainsLiteralSecret;
|
|
9
|
+
exports.decorateHostPlanWithConfiguredAgent = decorateHostPlanWithConfiguredAgent;
|
|
10
|
+
const fs_1 = __importDefault(require("fs"));
|
|
11
|
+
const path_1 = __importDefault(require("path"));
|
|
12
|
+
const project_fraim_paths_1 = require("../core/utils/project-fraim-paths");
|
|
13
|
+
const BASH_ENV_PREFIX = /^([A-Za-z_][A-Za-z0-9_]*)=(?:\$\([^)]*\)|"[^"]*"|'[^']*'|\S*)\s+/;
|
|
14
|
+
// The value may be a parenthesized PowerShell expression followed by a
|
|
15
|
+
// property-access chain (e.g. `(Get-Content ... | ConvertFrom-Json).api_key`),
|
|
16
|
+
// a quoted string, or a bare token — always terminated by the statement `;`.
|
|
17
|
+
const POWERSHELL_ENV_PREFIX = /^\$env:[A-Za-z_][A-Za-z0-9_]*\s*=\s*(?:\([^)]*\)(?:\.[A-Za-z_][A-Za-z0-9_]*)*|"[^"]*"|'[^']*'|\S+?)\s*;\s*/;
|
|
18
|
+
function parseConfiguredCommand(runner, commandText) {
|
|
19
|
+
let rest = String(commandText || '').trim();
|
|
20
|
+
const prefixPattern = runner === 'powershell' ? POWERSHELL_ENV_PREFIX : BASH_ENV_PREFIX;
|
|
21
|
+
let prefixEnd = 0;
|
|
22
|
+
while (true) {
|
|
23
|
+
const match = rest.slice(prefixEnd).match(prefixPattern);
|
|
24
|
+
if (!match)
|
|
25
|
+
break;
|
|
26
|
+
prefixEnd += match[0].length;
|
|
27
|
+
}
|
|
28
|
+
const envPrefixText = rest.slice(0, prefixEnd).trim();
|
|
29
|
+
const invocation = rest.slice(prefixEnd).trim();
|
|
30
|
+
const tokens = tokenizeShellLike(invocation);
|
|
31
|
+
return { envPrefixText, extraArgs: tokens.slice(1), invocationHead: tokens[0] || '' };
|
|
32
|
+
}
|
|
33
|
+
// Reviewer finding (PR #1209, Round 3): parseConfiguredCommand's leniency is
|
|
34
|
+
// intentional for *parsing* (it never throws), but that leniency means a
|
|
35
|
+
// structurally wrong command — most dangerously a shell built-in like `.`
|
|
36
|
+
// (source) or `source` at the head, meant to load a setup script into the
|
|
37
|
+
// current shell — parses "successfully" into a garbage invocationHead that
|
|
38
|
+
// decorateHostPlanWithConfiguredAgent then silently discards, appending only
|
|
39
|
+
// the path/args that followed it as bogus positional arguments to the real
|
|
40
|
+
// agent binary instead of ever sourcing the script. Since the invocation
|
|
41
|
+
// head is always discarded and replaced with Hub's own known base binary,
|
|
42
|
+
// the only way to catch this class of error is to check, at readiness time,
|
|
43
|
+
// that what the user typed there actually was that same base binary — not
|
|
44
|
+
// to attempt to execute or otherwise interpret it.
|
|
45
|
+
function describeConfiguredCommandInvocationProblem(parsed, baseHostId) {
|
|
46
|
+
if (!parsed.invocationHead) {
|
|
47
|
+
return 'Command has no invocation to run after the environment prefix.';
|
|
48
|
+
}
|
|
49
|
+
if (parsed.invocationHead !== baseHostId) {
|
|
50
|
+
return `Command must invoke "${baseHostId}" (found "${parsed.invocationHead}"). A setup script should be registered as a setup script, not composed into this field — see hub-configured-agent-setup.md.`;
|
|
51
|
+
}
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
function tokenizeShellLike(text) {
|
|
55
|
+
const tokens = [];
|
|
56
|
+
const re = /"([^"]*)"|'([^']*)'|(\S+)/g;
|
|
57
|
+
let match;
|
|
58
|
+
while ((match = re.exec(text)) !== null) {
|
|
59
|
+
tokens.push(match[1] ?? match[2] ?? match[3]);
|
|
60
|
+
}
|
|
61
|
+
return tokens;
|
|
62
|
+
}
|
|
63
|
+
// Issue #1201 (R2.3): UX safety net only — flags what pattern-matches as a
|
|
64
|
+
// literal high-entropy secret pasted into the command field. Not a substitute
|
|
65
|
+
// for the routing skill never generating such a command in the first place.
|
|
66
|
+
const LITERAL_SECRET_TOKEN = /^[A-Za-z0-9+/_.=-]{20,}$/;
|
|
67
|
+
function commandContainsLiteralSecret(commandText) {
|
|
68
|
+
for (const token of String(commandText || '').split(/\s+/)) {
|
|
69
|
+
const eq = token.indexOf('=');
|
|
70
|
+
if (eq <= 0)
|
|
71
|
+
continue;
|
|
72
|
+
const value = token.slice(eq + 1);
|
|
73
|
+
if (!value || value.startsWith('$') || value.startsWith('`') || value.startsWith('('))
|
|
74
|
+
continue;
|
|
75
|
+
if (LITERAL_SECRET_TOKEN.test(value))
|
|
76
|
+
return true;
|
|
77
|
+
}
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
// The env-prefix assignment is evaluated as a bash "temporary environment"
|
|
81
|
+
// directly attached to the `exec` command on the same line (`VAR=$(...) exec
|
|
82
|
+
// "$@"`), not as a separate statement before it. A bare `VAR=value` on its
|
|
83
|
+
// own line only sets a local shell variable — it is NOT exported, so it
|
|
84
|
+
// would silently vanish from the real binary's environment once `exec`
|
|
85
|
+
// replaces this process image. Attaching it to the same command line is
|
|
86
|
+
// exactly what a terminal user's `VAR=$(...) codex ...` already does.
|
|
87
|
+
const BASH_LAUNCH_WRAPPER = [
|
|
88
|
+
'#!/usr/bin/env bash',
|
|
89
|
+
'set -e',
|
|
90
|
+
'if [ -n "$FRAIM_CONFIGURED_AGENT_ENV_PREFIX" ]; then',
|
|
91
|
+
' eval "$FRAIM_CONFIGURED_AGENT_ENV_PREFIX exec \\"\\$@\\""',
|
|
92
|
+
'else',
|
|
93
|
+
' exec "$@"',
|
|
94
|
+
'fi',
|
|
95
|
+
'',
|
|
96
|
+
].join('\n');
|
|
97
|
+
const POWERSHELL_LAUNCH_WRAPPER = [
|
|
98
|
+
'$ErrorActionPreference = "Stop"',
|
|
99
|
+
'if ($env:FRAIM_CONFIGURED_AGENT_ENV_PREFIX) {',
|
|
100
|
+
' Invoke-Expression $env:FRAIM_CONFIGURED_AGENT_ENV_PREFIX',
|
|
101
|
+
'}',
|
|
102
|
+
'$exe = $args[0]',
|
|
103
|
+
'if ($args.Length -gt 1) { $rest = $args[1..($args.Length - 1)] } else { $rest = @() }',
|
|
104
|
+
'& $exe @rest',
|
|
105
|
+
'exit $LASTEXITCODE',
|
|
106
|
+
'',
|
|
107
|
+
].join('\r\n');
|
|
108
|
+
// Writes the small, fixed wrapper script that lets a configured-agent's
|
|
109
|
+
// env-prefix (which may contain `$(...)`/`(...)` substitutions) resolve in a
|
|
110
|
+
// real shell at spawn time, then `exec`s Hub's own required invocation with
|
|
111
|
+
// the parsed extra CLI args appended. Content-checked so it self-heals like
|
|
112
|
+
// any other FRAIM-generated artifact (issue #1206 precedent) rather than
|
|
113
|
+
// silently going stale if regenerated with different logic.
|
|
114
|
+
//
|
|
115
|
+
// Security review (issue #1201): written under the per-user FRAIM home
|
|
116
|
+
// (`getUserFraimDirPath()`), NOT `os.tmpdir()`. A predictable filename in a
|
|
117
|
+
// shared, world-writable OS temp directory is a symlink-race target on
|
|
118
|
+
// multi-user machines — another local user could pre-place a symlink at the
|
|
119
|
+
// predictable path, and `fs.writeFileSync` would follow it and overwrite an
|
|
120
|
+
// arbitrary file the *victim* user can write to. The per-user FRAIM home is
|
|
121
|
+
// not shared across OS accounts, closing that class of attack.
|
|
122
|
+
function ensureConfiguredAgentLaunchWrapper(runner) {
|
|
123
|
+
const content = runner === 'bash' ? BASH_LAUNCH_WRAPPER : POWERSHELL_LAUNCH_WRAPPER;
|
|
124
|
+
const filePath = path_1.default.join((0, project_fraim_paths_1.getUserFraimDirPath)(), runner === 'bash' ? 'hub-configured-agent-launch.sh' : 'hub-configured-agent-launch.ps1');
|
|
125
|
+
const existing = fs_1.default.existsSync(filePath) ? fs_1.default.readFileSync(filePath, 'utf8') : null;
|
|
126
|
+
if (existing !== content) {
|
|
127
|
+
fs_1.default.writeFileSync(filePath, content, { encoding: 'utf8', mode: 0o755 });
|
|
128
|
+
}
|
|
129
|
+
return filePath;
|
|
130
|
+
}
|
|
131
|
+
function decorateHostPlanWithConfiguredAgent(plan, agent, agentEnv = {}) {
|
|
132
|
+
if (!agent)
|
|
133
|
+
return plan;
|
|
134
|
+
const withEnv = { ...plan, env: { ...(plan.env || {}), ...agentEnv } };
|
|
135
|
+
if (!agent.command)
|
|
136
|
+
return withEnv;
|
|
137
|
+
const parsed = parseConfiguredCommand(agent.command.runner, agent.command.command);
|
|
138
|
+
if (!parsed.envPrefixText && parsed.extraArgs.length === 0)
|
|
139
|
+
return withEnv;
|
|
140
|
+
const wrapperPath = ensureConfiguredAgentLaunchWrapper(agent.command.runner);
|
|
141
|
+
const realInvocation = [plan.command, ...plan.args, ...parsed.extraArgs];
|
|
142
|
+
const decoratedEnv = { ...withEnv.env, FRAIM_CONFIGURED_AGENT_ENV_PREFIX: parsed.envPrefixText };
|
|
143
|
+
if (agent.command.runner === 'bash') {
|
|
144
|
+
return { ...withEnv, command: 'bash', args: [wrapperPath, ...realInvocation], env: decoratedEnv };
|
|
145
|
+
}
|
|
146
|
+
return {
|
|
147
|
+
...withEnv,
|
|
148
|
+
command: 'powershell.exe',
|
|
149
|
+
args: ['-NoProfile', '-ExecutionPolicy', 'Bypass', '-File', wrapperPath, ...realInvocation],
|
|
150
|
+
env: decoratedEnv,
|
|
151
|
+
};
|
|
152
|
+
}
|
|
@@ -3,18 +3,18 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.AiHubConfiguredAgentStore = void 0;
|
|
6
|
+
exports.describeConfiguredCommandInvocationProblem = exports.decorateHostPlanWithConfiguredAgent = exports.commandContainsLiteralSecret = exports.parseConfiguredCommand = exports.AiHubConfiguredAgentStore = void 0;
|
|
7
7
|
exports.synthesizeDefaultConfiguredAgents = synthesizeDefaultConfiguredAgents;
|
|
8
8
|
exports.checkConfiguredAgentAvailability = checkConfiguredAgentAvailability;
|
|
9
9
|
exports.checkConfiguredAgentReadiness = checkConfiguredAgentReadiness;
|
|
10
10
|
exports.projectConfiguredAgent = projectConfiguredAgent;
|
|
11
11
|
exports.resolveConfiguredAgentForHost = resolveConfiguredAgentForHost;
|
|
12
12
|
exports.resolveConfiguredAgentEnv = resolveConfiguredAgentEnv;
|
|
13
|
-
exports.decorateHostPlanWithConfiguredAgent = decorateHostPlanWithConfiguredAgent;
|
|
14
13
|
const fs_1 = __importDefault(require("fs"));
|
|
15
14
|
const path_1 = __importDefault(require("path"));
|
|
16
15
|
const child_process_1 = require("child_process");
|
|
17
16
|
const project_fraim_paths_1 = require("../core/utils/project-fraim-paths");
|
|
17
|
+
const configured_agent_command_1 = require("./configured-agent-command");
|
|
18
18
|
const VALID_HOSTS = ['codex', 'claude', 'gemini', 'copilot'];
|
|
19
19
|
const ENV_NAME_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
20
20
|
function nowIso() {
|
|
@@ -48,6 +48,9 @@ function normalizeAgent(input) {
|
|
|
48
48
|
const setupScript = normalizeSetupScript(input.setupScript);
|
|
49
49
|
if (input.setupScript && !setupScript)
|
|
50
50
|
return { ok: false, error: 'Setup script command is required.' };
|
|
51
|
+
const command = normalizeCommand(input.command);
|
|
52
|
+
if (input.command && !command)
|
|
53
|
+
return { ok: false, error: 'Command is required.' };
|
|
51
54
|
const createdAt = input.createdAt || nowIso();
|
|
52
55
|
return {
|
|
53
56
|
ok: true,
|
|
@@ -57,6 +60,7 @@ function normalizeAgent(input) {
|
|
|
57
60
|
...(input.description && String(input.description).trim() ? { description: String(input.description).trim() } : {}),
|
|
58
61
|
baseHostId,
|
|
59
62
|
enabled: input.enabled !== false,
|
|
63
|
+
...(command ? { command } : {}),
|
|
60
64
|
...(setupScript ? { setupScript } : {}),
|
|
61
65
|
createdAt,
|
|
62
66
|
updatedAt: nowIso(),
|
|
@@ -77,6 +81,24 @@ function normalizeSetupScript(value) {
|
|
|
77
81
|
timeoutMs: typeof value.timeoutMs === 'number' && value.timeoutMs > 0 ? Math.min(value.timeoutMs, 120_000) : 30_000,
|
|
78
82
|
};
|
|
79
83
|
}
|
|
84
|
+
// Issue #1201 (R2.1): the single-text-field setup surface. `runner` is an
|
|
85
|
+
// internal execution detail — callers that omit it get the current platform's
|
|
86
|
+
// default shell so the UI never has to ask the user to pick one.
|
|
87
|
+
function normalizeCommand(value) {
|
|
88
|
+
if (!value)
|
|
89
|
+
return null;
|
|
90
|
+
const command = String(value.command || '').trim();
|
|
91
|
+
if (!command)
|
|
92
|
+
return null;
|
|
93
|
+
const runner = value.runner === 'bash' || value.runner === 'powershell'
|
|
94
|
+
? value.runner
|
|
95
|
+
: (process.platform === 'win32' ? 'powershell' : 'bash');
|
|
96
|
+
return {
|
|
97
|
+
runner,
|
|
98
|
+
command,
|
|
99
|
+
timeoutMs: typeof value.timeoutMs === 'number' && value.timeoutMs > 0 ? Math.min(value.timeoutMs, 120_000) : 30_000,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
80
102
|
function synthesizeDefaultConfiguredAgents(employees) {
|
|
81
103
|
const timestamp = '1970-01-01T00:00:00.000Z';
|
|
82
104
|
return employees
|
|
@@ -152,7 +174,12 @@ function checkConfiguredAgentAvailability(agent, employees, env = process.env) {
|
|
|
152
174
|
reasons.push('Configured agent is disabled.');
|
|
153
175
|
if (!employee?.available)
|
|
154
176
|
reasons.push(`${employee?.label || agent.baseHostId} is not available on this machine.`);
|
|
155
|
-
if (agent.
|
|
177
|
+
if (agent.command) {
|
|
178
|
+
if (!agent.command.command.trim()) {
|
|
179
|
+
reasons.push('Command is empty.');
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
else if (agent.setupScript) {
|
|
156
183
|
const command = agent.setupScript.command;
|
|
157
184
|
const isPathLike = /[\\/]/.test(command) || /^[A-Za-z]:[\\/]/.test(command);
|
|
158
185
|
if (isPathLike && !fs_1.default.existsSync(command)) {
|
|
@@ -160,13 +187,35 @@ function checkConfiguredAgentAvailability(agent, employees, env = process.env) {
|
|
|
160
187
|
}
|
|
161
188
|
}
|
|
162
189
|
else if (!agent.id.endsWith('-default')) {
|
|
163
|
-
warnings.push('No
|
|
190
|
+
warnings.push('No command configured; this profile launches the base CLI unchanged.');
|
|
164
191
|
}
|
|
165
192
|
return { id: agent.id, label: agent.label, baseHostId: agent.baseHostId, enabled: agent.enabled, available: reasons.length === 0, reasons, warnings };
|
|
166
193
|
}
|
|
167
194
|
function checkConfiguredAgentReadiness(agent, employees, env = process.env) {
|
|
168
195
|
const check = checkConfiguredAgentAvailability(agent, employees, env);
|
|
169
|
-
if (!check.available
|
|
196
|
+
if (!check.available)
|
|
197
|
+
return check;
|
|
198
|
+
if (agent.command) {
|
|
199
|
+
try {
|
|
200
|
+
const parsed = (0, configured_agent_command_1.parseConfiguredCommand)(agent.command.runner, agent.command.command);
|
|
201
|
+
const problem = (0, configured_agent_command_1.describeConfiguredCommandInvocationProblem)(parsed, agent.baseHostId);
|
|
202
|
+
if (problem) {
|
|
203
|
+
return { ...check, available: false, reasons: [...check.reasons, problem] };
|
|
204
|
+
}
|
|
205
|
+
return check;
|
|
206
|
+
}
|
|
207
|
+
catch (error) {
|
|
208
|
+
return {
|
|
209
|
+
...check,
|
|
210
|
+
available: false,
|
|
211
|
+
reasons: [
|
|
212
|
+
...check.reasons,
|
|
213
|
+
error instanceof Error ? error.message : 'Configured agent command could not be parsed.',
|
|
214
|
+
],
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
if (!agent.setupScript)
|
|
170
219
|
return check;
|
|
171
220
|
try {
|
|
172
221
|
resolveConfiguredAgentEnv(agent);
|
|
@@ -191,6 +240,7 @@ function projectConfiguredAgent(agent, employees) {
|
|
|
191
240
|
...(agent.description ? { description: agent.description } : {}),
|
|
192
241
|
baseHostId: agent.baseHostId,
|
|
193
242
|
enabled: agent.enabled,
|
|
243
|
+
...(agent.command ? { command: { ...agent.command } } : {}),
|
|
194
244
|
...(agent.setupScript ? { setupScript: { ...agent.setupScript, args: [...(agent.setupScript.args || [])] } } : {}),
|
|
195
245
|
createdAt: agent.createdAt,
|
|
196
246
|
updatedAt: agent.updatedAt,
|
|
@@ -295,11 +345,13 @@ function filterSafeEnv(env) {
|
|
|
295
345
|
}
|
|
296
346
|
return safe;
|
|
297
347
|
}
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
}
|
|
348
|
+
// Issue #1201: command parsing, secret detection, and launch decoration for
|
|
349
|
+
// the single-command configured-agent surface live in their own module
|
|
350
|
+
// (kept a cohesive, separately-testable unit rather than growing this file
|
|
351
|
+
// past the 500-line quality threshold) — re-exported here so existing
|
|
352
|
+
// callers importing from `./configured-agents` are unaffected.
|
|
353
|
+
var configured_agent_command_2 = require("./configured-agent-command");
|
|
354
|
+
Object.defineProperty(exports, "parseConfiguredCommand", { enumerable: true, get: function () { return configured_agent_command_2.parseConfiguredCommand; } });
|
|
355
|
+
Object.defineProperty(exports, "commandContainsLiteralSecret", { enumerable: true, get: function () { return configured_agent_command_2.commandContainsLiteralSecret; } });
|
|
356
|
+
Object.defineProperty(exports, "decorateHostPlanWithConfiguredAgent", { enumerable: true, get: function () { return configured_agent_command_2.decorateHostPlanWithConfiguredAgent; } });
|
|
357
|
+
Object.defineProperty(exports, "describeConfiguredCommandInvocationProblem", { enumerable: true, get: function () { return configured_agent_command_2.describeConfiguredCommandInvocationProblem; } });
|
|
@@ -107,7 +107,7 @@ function configureAutoUpdater() {
|
|
|
107
107
|
function ensureWordSideload(projectPath, httpsPort) {
|
|
108
108
|
// Flag version bump: bump this string when new manifests are added so all
|
|
109
109
|
// users get re-sideloaded on their next launch.
|
|
110
|
-
const FLAG_VERSION = '
|
|
110
|
+
const FLAG_VERSION = 'v6-excel-addin';
|
|
111
111
|
const expectedFlag = `${FLAG_VERSION}:${httpsPort}`;
|
|
112
112
|
const flagPath = path_1.default.join(electron_1.app.getPath('userData'), 'word-sideloaded.flag');
|
|
113
113
|
const existingFlag = fs_1.default.existsSync(flagPath) ? fs_1.default.readFileSync(flagPath, 'utf8') : null;
|
|
@@ -4,28 +4,57 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.getLatestPublishedVersion = getLatestPublishedVersion;
|
|
7
|
+
exports.getCachedLatestPublishedVersion = getCachedLatestPublishedVersion;
|
|
8
|
+
exports.refreshLatestPublishedVersionInBackground = refreshLatestPublishedVersionInBackground;
|
|
7
9
|
exports.__resetLatestVersionCache = __resetLatestVersionCache;
|
|
8
10
|
const https_1 = __importDefault(require("https"));
|
|
9
11
|
// Issue #755: best-effort "latest published fraim version" for the Hub's
|
|
10
12
|
// update-available nudge. Cached in-process (1h TTL) and never throws — returns
|
|
11
13
|
// null when offline or the registry is unreachable, so the nudge simply hides.
|
|
12
14
|
const TTL_MS = 60 * 60 * 1000;
|
|
15
|
+
const FAILED_RETRY_MS = 5 * 60 * 1000;
|
|
13
16
|
const REGISTRY_URL = 'https://registry.npmjs.org/fraim/latest';
|
|
14
17
|
let cache = { value: null, at: 0 };
|
|
18
|
+
let lastAttemptAt = 0;
|
|
19
|
+
let inFlight = null;
|
|
15
20
|
async function getLatestPublishedVersion() {
|
|
16
21
|
const now = Date.now();
|
|
17
22
|
if (cache.at !== 0 && now - cache.at < TTL_MS)
|
|
18
23
|
return cache.value;
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
24
|
+
return refreshLatestPublishedVersion();
|
|
25
|
+
}
|
|
26
|
+
function getCachedLatestPublishedVersion() {
|
|
27
|
+
return cache.value;
|
|
28
|
+
}
|
|
29
|
+
function refreshLatestPublishedVersionInBackground() {
|
|
30
|
+
void refreshLatestPublishedVersion();
|
|
31
|
+
}
|
|
32
|
+
async function refreshLatestPublishedVersion() {
|
|
33
|
+
const now = Date.now();
|
|
34
|
+
if (cache.at !== 0 && now - cache.at < TTL_MS)
|
|
35
|
+
return cache.value;
|
|
36
|
+
if (inFlight)
|
|
37
|
+
return inFlight;
|
|
38
|
+
if (cache.value === null && lastAttemptAt !== 0 && now - lastAttemptAt < FAILED_RETRY_MS) {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
lastAttemptAt = now;
|
|
42
|
+
inFlight = fetchNpmLatest()
|
|
43
|
+
.then((value) => {
|
|
44
|
+
if (value !== null)
|
|
45
|
+
cache = { value, at: Date.now() };
|
|
46
|
+
return value ?? cache.value;
|
|
47
|
+
})
|
|
48
|
+
.finally(() => {
|
|
49
|
+
inFlight = null;
|
|
50
|
+
});
|
|
51
|
+
return inFlight;
|
|
25
52
|
}
|
|
26
53
|
/** Test-only: reset the module cache. */
|
|
27
54
|
function __resetLatestVersionCache() {
|
|
28
55
|
cache = { value: null, at: 0 };
|
|
56
|
+
lastAttemptAt = 0;
|
|
57
|
+
inFlight = null;
|
|
29
58
|
}
|
|
30
59
|
function fetchNpmLatest() {
|
|
31
60
|
return new Promise((resolve) => {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
|
-
* Sideloads Office add-in manifests (Word + PowerPoint) so they appear
|
|
4
|
-
* Insert > My Add-ins > Developer Add-ins without admin rights or AppSource.
|
|
3
|
+
* Sideloads Office add-in manifests (Word + PowerPoint + Excel) so they appear
|
|
4
|
+
* under Insert > My Add-ins > Developer Add-ins without admin rights or AppSource.
|
|
5
5
|
*
|
|
6
6
|
* Windows: writes HKCU\SOFTWARE\Microsoft\Office\16.0\WEF\Developer\{<guid>}
|
|
7
7
|
* (Default) = ABSOLUTE FILE PATH to manifest.xml. This is the registry
|
|
@@ -52,6 +52,15 @@ const MANIFESTS = [
|
|
|
52
52
|
],
|
|
53
53
|
macContainer: 'com.microsoft.Powerpoint',
|
|
54
54
|
},
|
|
55
|
+
{
|
|
56
|
+
guid: 'da8d22c7-2c00-485a-bdc9-13dda7f80c48',
|
|
57
|
+
candidates: (base) => [
|
|
58
|
+
path_1.default.resolve(base, 'public/ai-hub/excel-taskpane/manifest.xml'),
|
|
59
|
+
path_1.default.resolve(__dirname, '..', '..', 'public/ai-hub/excel-taskpane/manifest.xml'),
|
|
60
|
+
path_1.default.resolve(__dirname, '..', '..', '..', 'public/ai-hub/excel-taskpane/manifest.xml'),
|
|
61
|
+
],
|
|
62
|
+
macContainer: 'com.microsoft.Excel',
|
|
63
|
+
},
|
|
55
64
|
];
|
|
56
65
|
function resolveManifestPath(entry, projectPath) {
|
|
57
66
|
return entry.candidates(projectPath).find(c => fs_1.default.existsSync(c)) ?? null;
|
|
@@ -157,7 +166,7 @@ function sideloadManifest(projectPath, options = {}) {
|
|
|
157
166
|
for (const entry of MANIFESTS) {
|
|
158
167
|
const manifestPath = resolveManifestPath(entry, projectPath);
|
|
159
168
|
if (!manifestPath) {
|
|
160
|
-
return { ok: false, reason: `Manifest not found for GUID ${entry.guid} — check extensions/office-word/ and public/ai-hub/
|
|
169
|
+
return { ok: false, reason: `Manifest not found for GUID ${entry.guid} — check extensions/office-word/, public/ai-hub/powerpoint-taskpane/, and public/ai-hub/excel-taskpane/` };
|
|
161
170
|
}
|
|
162
171
|
const sideloadPath = prepareManifestForSideload(entry, manifestPath, options);
|
|
163
172
|
if (process.platform === 'win32') {
|