@interchained/portal-agent 0.1.1 → 0.1.3
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/generate.d.ts +26 -15
- package/dist/generate.d.ts.map +1 -1
- package/dist/generate.js +35 -32
- package/dist/generate.js.map +1 -1
- package/dist/sentinel.d.ts +51 -8
- package/dist/sentinel.d.ts.map +1 -1
- package/dist/sentinel.js +133 -27
- package/dist/sentinel.js.map +1 -1
- package/package.json +11 -10
package/dist/generate.d.ts
CHANGED
|
@@ -1,41 +1,52 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Page / component generation.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* Flow when Sentinel is present:
|
|
5
|
+
* Runner generates → Sentinel.reviewAndApply() → applied or blocked
|
|
6
|
+
*
|
|
7
|
+
* Flow without Sentinel (--no-sentinel or API key absent):
|
|
8
|
+
* Runner generates → patch saved as "pending" → human approves via CLI
|
|
7
9
|
*/
|
|
8
10
|
import type { AppContract, PageContract } from "@interchained/portal-contract";
|
|
9
11
|
import { Runner } from "./runner.js";
|
|
10
|
-
import { Sentinel } from "./sentinel.js";
|
|
11
|
-
import { type Patch } from "./patch.js";
|
|
12
|
+
import { Sentinel, type SentinelReview } from "./sentinel.js";
|
|
13
|
+
import { PatchStore, type Patch } from "./patch.js";
|
|
12
14
|
export interface GenerateOptions {
|
|
13
15
|
runner: Runner;
|
|
14
16
|
sentinel?: Sentinel;
|
|
15
17
|
contract: AppContract;
|
|
16
18
|
projectRoot: string;
|
|
17
|
-
|
|
19
|
+
route?: string;
|
|
20
|
+
/** Only consulted when NO sentinel is present */
|
|
18
21
|
requiresApproval?: boolean;
|
|
19
22
|
}
|
|
20
23
|
export interface GenerateResult {
|
|
21
24
|
patch: Patch;
|
|
22
|
-
|
|
23
|
-
|
|
25
|
+
/** Set whenever a Sentinel ran */
|
|
26
|
+
sentinelReview?: SentinelReview;
|
|
27
|
+
/**
|
|
28
|
+
* True when the Sentinel applied the patch (clean or self-corrected).
|
|
29
|
+
* False when no sentinel ran — human approval prompt shown instead.
|
|
30
|
+
*/
|
|
31
|
+
sentinelApplied: boolean;
|
|
32
|
+
/**
|
|
33
|
+
* Populated when the Sentinel had to self-correct the patch before applying.
|
|
34
|
+
* Undefined when the patch was clean.
|
|
35
|
+
*/
|
|
36
|
+
sentinelFix?: {
|
|
24
37
|
summary: string;
|
|
25
|
-
|
|
38
|
+
changes: string[];
|
|
26
39
|
};
|
|
27
40
|
}
|
|
28
41
|
/**
|
|
29
|
-
* Generate a new page component from a PageContract
|
|
42
|
+
* Generate a new page component from a PageContract.
|
|
30
43
|
*/
|
|
31
44
|
export declare function generatePage(page: PageContract & {
|
|
32
45
|
description?: string;
|
|
33
|
-
}, opts: GenerateOptions): Promise<GenerateResult>;
|
|
46
|
+
}, opts: GenerateOptions, store: PatchStore): Promise<GenerateResult>;
|
|
34
47
|
/**
|
|
35
|
-
* Generate a page from a freeform description
|
|
48
|
+
* Generate a page from a freeform description.
|
|
36
49
|
* `portal generate page "Father's Day promo"` style.
|
|
37
50
|
*/
|
|
38
|
-
export declare function generateFromPrompt(prompt: string, opts: GenerateOptions
|
|
39
|
-
route?: string;
|
|
40
|
-
}): Promise<GenerateResult>;
|
|
51
|
+
export declare function generateFromPrompt(prompt: string, opts: GenerateOptions, store?: PatchStore): Promise<GenerateResult>;
|
|
41
52
|
//# sourceMappingURL=generate.d.ts.map
|
package/dist/generate.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generate.d.ts","sourceRoot":"","sources":["../src/generate.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"generate.d.ts","sourceRoot":"","sources":["../src/generate.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAIH,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAC/E,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,QAAQ,EAAE,KAAK,cAAc,EAAE,MAAM,eAAe,CAAC;AAC9D,OAAO,EAAe,UAAU,EAAE,KAAK,KAAK,EAAE,MAAM,YAAY,CAAC;AAEjE,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,QAAQ,EAAE,WAAW,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,iDAAiD;IACjD,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B;AAED,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,KAAK,CAAC;IACb,kCAAkC;IAClC,cAAc,CAAC,EAAE,cAAc,CAAC;IAChC;;;OAGG;IACH,eAAe,EAAE,OAAO,CAAC;IACzB;;;OAGG;IACH,WAAW,CAAC,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC;CACtD;AAED;;GAEG;AACH,wBAAsB,YAAY,CAChC,IAAI,EAAE,YAAY,GAAG;IAAE,WAAW,CAAC,EAAE,MAAM,CAAA;CAAE,EAC7C,IAAI,EAAE,eAAe,EACrB,KAAK,EAAE,UAAU,GAChB,OAAO,CAAC,cAAc,CAAC,CAyDzB;AAED;;;GAGG;AACH,wBAAsB,kBAAkB,CACtC,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,eAAe,EACrB,KAAK,CAAC,EAAE,UAAU,GACjB,OAAO,CAAC,cAAc,CAAC,CAUzB"}
|
package/dist/generate.js
CHANGED
|
@@ -1,19 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Page / component generation.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* Flow when Sentinel is present:
|
|
5
|
+
* Runner generates → Sentinel.reviewAndApply() → applied or blocked
|
|
6
|
+
*
|
|
7
|
+
* Flow without Sentinel (--no-sentinel or API key absent):
|
|
8
|
+
* Runner generates → patch saved as "pending" → human approves via CLI
|
|
7
9
|
*/
|
|
8
10
|
import { readFile } from "node:fs/promises";
|
|
9
11
|
import { join } from "node:path";
|
|
10
|
-
import { createPatch } from "./patch.js";
|
|
12
|
+
import { createPatch, PatchStore } from "./patch.js";
|
|
11
13
|
/**
|
|
12
|
-
* Generate a new page component from a PageContract
|
|
14
|
+
* Generate a new page component from a PageContract.
|
|
13
15
|
*/
|
|
14
|
-
export async function generatePage(page, opts) {
|
|
16
|
+
export async function generatePage(page, opts, store) {
|
|
15
17
|
const { runner, sentinel, contract } = opts;
|
|
16
|
-
// Check if the file already exists — use as context
|
|
17
18
|
const fileName = routeToFileName(page.route);
|
|
18
19
|
const filePath = join(opts.projectRoot, "routes", fileName);
|
|
19
20
|
let original = "";
|
|
@@ -21,7 +22,7 @@ export async function generatePage(page, opts) {
|
|
|
21
22
|
original = await readFile(filePath, "utf-8");
|
|
22
23
|
}
|
|
23
24
|
catch {
|
|
24
|
-
// New file
|
|
25
|
+
// New file
|
|
25
26
|
}
|
|
26
27
|
const proposed = await runner.generateComponent({
|
|
27
28
|
route: page.route,
|
|
@@ -31,23 +32,7 @@ export async function generatePage(page, opts) {
|
|
|
31
32
|
brandVoice: contract.brand?.voice,
|
|
32
33
|
colors: contract.brand?.colors,
|
|
33
34
|
});
|
|
34
|
-
const requiresApproval = opts.requiresApproval ??
|
|
35
|
-
contract.policies?.publishing === "human_review";
|
|
36
|
-
let sentinelResult;
|
|
37
|
-
if (sentinel) {
|
|
38
|
-
const review = await sentinel.review({
|
|
39
|
-
contract,
|
|
40
|
-
filePath,
|
|
41
|
-
original,
|
|
42
|
-
proposed,
|
|
43
|
-
agentTask: `Generate page for route "${page.route}": ${page.purpose}`,
|
|
44
|
-
});
|
|
45
|
-
sentinelResult = {
|
|
46
|
-
approved: review.approved,
|
|
47
|
-
summary: review.summary,
|
|
48
|
-
violations: review.violations,
|
|
49
|
-
};
|
|
50
|
-
}
|
|
35
|
+
const requiresApproval = opts.requiresApproval ?? contract.policies?.publishing === "human_review";
|
|
51
36
|
const patch = createPatch({
|
|
52
37
|
agent: "portal-generate",
|
|
53
38
|
file: join("routes", fileName),
|
|
@@ -55,17 +40,35 @@ export async function generatePage(page, opts) {
|
|
|
55
40
|
proposed,
|
|
56
41
|
reason: `Generate page: ${page.purpose}`,
|
|
57
42
|
requiresApproval,
|
|
58
|
-
sentinelApproved: sentinelResult?.approved,
|
|
59
|
-
sentinelSummary: sentinelResult?.summary,
|
|
60
|
-
sentinelViolations: sentinelResult?.violations,
|
|
61
43
|
});
|
|
62
|
-
|
|
44
|
+
await store.save(patch);
|
|
45
|
+
// ── Sentinel is the gate + applier ─────────────────────────────────────────
|
|
46
|
+
if (sentinel) {
|
|
47
|
+
const result = await sentinel.reviewAndApply({
|
|
48
|
+
patch,
|
|
49
|
+
projectRoot: opts.projectRoot,
|
|
50
|
+
store,
|
|
51
|
+
contract,
|
|
52
|
+
agentTask: `Generate page for route "${page.route}": ${page.purpose}`,
|
|
53
|
+
});
|
|
54
|
+
return {
|
|
55
|
+
patch,
|
|
56
|
+
sentinelReview: result.review,
|
|
57
|
+
sentinelApplied: result.applied,
|
|
58
|
+
sentinelFix: result.fix
|
|
59
|
+
? { summary: result.fix.summary, changes: result.fix.changes }
|
|
60
|
+
: undefined,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
// ── No sentinel — patch stays "pending", human decides ────────────────────
|
|
64
|
+
return { patch, sentinelApplied: false };
|
|
63
65
|
}
|
|
64
66
|
/**
|
|
65
|
-
* Generate a page from a freeform description
|
|
67
|
+
* Generate a page from a freeform description.
|
|
66
68
|
* `portal generate page "Father's Day promo"` style.
|
|
67
69
|
*/
|
|
68
|
-
export async function generateFromPrompt(prompt, opts) {
|
|
70
|
+
export async function generateFromPrompt(prompt, opts, store) {
|
|
71
|
+
const resolvedStore = store ?? new PatchStore(opts.projectRoot);
|
|
69
72
|
const route = opts.route ?? promptToRoute(prompt);
|
|
70
73
|
const page = {
|
|
71
74
|
route,
|
|
@@ -73,7 +76,7 @@ export async function generateFromPrompt(prompt, opts) {
|
|
|
73
76
|
audience: undefined,
|
|
74
77
|
primaryAction: undefined,
|
|
75
78
|
};
|
|
76
|
-
return generatePage(page, opts);
|
|
79
|
+
return generatePage(page, opts, resolvedStore);
|
|
77
80
|
}
|
|
78
81
|
// ── Helpers ───────────────────────────────────────────────────────────────────
|
|
79
82
|
function routeToFileName(route) {
|
package/dist/generate.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generate.js","sourceRoot":"","sources":["../src/generate.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"generate.js","sourceRoot":"","sources":["../src/generate.ts"],"names":[],"mappings":"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"}
|
package/dist/sentinel.d.ts
CHANGED
|
@@ -1,29 +1,50 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Sentinel — reviews
|
|
2
|
+
* Sentinel — reviews, auto-corrects, and applies patches.
|
|
3
3
|
*
|
|
4
|
-
* The
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
4
|
+
* The Sentinel never just rejects. Its contract:
|
|
5
|
+
* 1. Review the Runner's output against the app contract
|
|
6
|
+
* 2. If clean → apply as-is
|
|
7
|
+
* 3. If issues → self-correct (call AI to fix the violations)
|
|
8
|
+
* 4. Apply the corrected version
|
|
9
|
+
* 5. Report what was fixed
|
|
10
|
+
*
|
|
11
|
+
* The human always sees a result, never a gate.
|
|
12
|
+
* The only terminal failure is an unrecoverable AI error.
|
|
9
13
|
*/
|
|
10
14
|
import { type AiAssistConfig } from "./aiassist.js";
|
|
11
15
|
import type { AppContract } from "@interchained/portal-contract";
|
|
16
|
+
import { type Patch, type PatchStore } from "./patch.js";
|
|
12
17
|
export interface SentinelReview {
|
|
13
18
|
approved: boolean;
|
|
14
19
|
summary: string;
|
|
15
20
|
violations: string[];
|
|
16
|
-
/** Diff-friendly line annotations */
|
|
17
21
|
annotations: Array<{
|
|
18
22
|
line?: number;
|
|
19
23
|
type: "ok" | "warning" | "violation";
|
|
20
24
|
note: string;
|
|
21
25
|
}>;
|
|
22
26
|
}
|
|
27
|
+
export interface SentinelFix {
|
|
28
|
+
/** Plain-English summary of what the sentinel changed */
|
|
29
|
+
summary: string;
|
|
30
|
+
/** Each item is one fix the sentinel applied */
|
|
31
|
+
changes: string[];
|
|
32
|
+
/** The corrected file content */
|
|
33
|
+
correctedContent: string;
|
|
34
|
+
}
|
|
35
|
+
export interface SentinelResult {
|
|
36
|
+
review: SentinelReview;
|
|
37
|
+
/** Always true — sentinel either applied clean or applied its own fix */
|
|
38
|
+
applied: true;
|
|
39
|
+
/**
|
|
40
|
+
* Populated when the sentinel had to self-correct.
|
|
41
|
+
* Undefined when the patch was clean and applied as-is.
|
|
42
|
+
*/
|
|
43
|
+
fix?: SentinelFix;
|
|
44
|
+
}
|
|
23
45
|
export declare class Sentinel {
|
|
24
46
|
private client;
|
|
25
47
|
constructor(config?: Partial<AiAssistConfig>);
|
|
26
|
-
/** Review a generated patch against the app contract */
|
|
27
48
|
review(opts: {
|
|
28
49
|
contract: AppContract;
|
|
29
50
|
filePath: string;
|
|
@@ -31,5 +52,27 @@ export declare class Sentinel {
|
|
|
31
52
|
proposed: string;
|
|
32
53
|
agentTask: string;
|
|
33
54
|
}): Promise<SentinelReview>;
|
|
55
|
+
fix(opts: {
|
|
56
|
+
contract: AppContract;
|
|
57
|
+
filePath: string;
|
|
58
|
+
original: string;
|
|
59
|
+
proposed: string;
|
|
60
|
+
violations: string[];
|
|
61
|
+
agentTask: string;
|
|
62
|
+
}): Promise<SentinelFix>;
|
|
63
|
+
/**
|
|
64
|
+
* The primary entry point.
|
|
65
|
+
*
|
|
66
|
+
* Always applies something to disk. Never rejects to the caller.
|
|
67
|
+
* - Clean patch → apply as-is, fix undefined
|
|
68
|
+
* - Dirty patch → self-correct → apply corrected version, fix populated
|
|
69
|
+
*/
|
|
70
|
+
reviewAndApply(opts: {
|
|
71
|
+
patch: Patch;
|
|
72
|
+
projectRoot: string;
|
|
73
|
+
store: PatchStore;
|
|
74
|
+
contract: AppContract;
|
|
75
|
+
agentTask: string;
|
|
76
|
+
}): Promise<SentinelResult>;
|
|
34
77
|
}
|
|
35
78
|
//# sourceMappingURL=sentinel.d.ts.map
|
package/dist/sentinel.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sentinel.d.ts","sourceRoot":"","sources":["../src/sentinel.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"sentinel.d.ts","sourceRoot":"","sources":["../src/sentinel.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAkB,KAAK,cAAc,EAAE,MAAM,eAAe,CAAC;AACpE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,+BAA+B,CAAC;AACjE,OAAO,EAAc,KAAK,KAAK,EAAE,KAAK,UAAU,EAAE,MAAM,YAAY,CAAC;AAIrE,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,OAAO,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,WAAW,EAAE,KAAK,CAAC;QACjB,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,IAAI,EAAE,IAAI,GAAG,SAAS,GAAG,WAAW,CAAC;QACrC,IAAI,EAAE,MAAM,CAAC;KACd,CAAC,CAAC;CACJ;AAED,MAAM,WAAW,WAAW;IAC1B,yDAAyD;IACzD,OAAO,EAAE,MAAM,CAAC;IAChB,gDAAgD;IAChD,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,iCAAiC;IACjC,gBAAgB,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,cAAc,CAAC;IACvB,yEAAyE;IACzE,OAAO,EAAE,IAAI,CAAC;IACd;;;OAGG;IACH,GAAG,CAAC,EAAE,WAAW,CAAC;CACnB;AAID,qBAAa,QAAQ;IACnB,OAAO,CAAC,MAAM,CAAiB;gBAEnB,MAAM,GAAE,OAAO,CAAC,cAAc,CAAM;IAe1C,MAAM,CAAC,IAAI,EAAE;QACjB,QAAQ,EAAE,WAAW,CAAC;QACtB,QAAQ,EAAE,MAAM,CAAC;QACjB,QAAQ,EAAE,MAAM,CAAC;QACjB,QAAQ,EAAE,MAAM,CAAC;QACjB,SAAS,EAAE,MAAM,CAAC;KACnB,GAAG,OAAO,CAAC,cAAc,CAAC;IA0DrB,GAAG,CAAC,IAAI,EAAE;QACd,QAAQ,EAAE,WAAW,CAAC;QACtB,QAAQ,EAAE,MAAM,CAAC;QACjB,QAAQ,EAAE,MAAM,CAAC;QACjB,QAAQ,EAAE,MAAM,CAAC;QACjB,UAAU,EAAE,MAAM,EAAE,CAAC;QACrB,SAAS,EAAE,MAAM,CAAC;KACnB,GAAG,OAAO,CAAC,WAAW,CAAC;IAqDxB;;;;;;OAMG;IACG,cAAc,CAAC,IAAI,EAAE;QACzB,KAAK,EAAE,KAAK,CAAC;QACb,WAAW,EAAE,MAAM,CAAC;QACpB,KAAK,EAAE,UAAU,CAAC;QAClB,QAAQ,EAAE,WAAW,CAAC;QACtB,SAAS,EAAE,MAAM,CAAC;KACnB,GAAG,OAAO,CAAC,cAAc,CAAC;CA2C5B"}
|
package/dist/sentinel.js
CHANGED
|
@@ -1,32 +1,40 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Sentinel — reviews
|
|
2
|
+
* Sentinel — reviews, auto-corrects, and applies patches.
|
|
3
3
|
*
|
|
4
|
-
* The
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
4
|
+
* The Sentinel never just rejects. Its contract:
|
|
5
|
+
* 1. Review the Runner's output against the app contract
|
|
6
|
+
* 2. If clean → apply as-is
|
|
7
|
+
* 3. If issues → self-correct (call AI to fix the violations)
|
|
8
|
+
* 4. Apply the corrected version
|
|
9
|
+
* 5. Report what was fixed
|
|
10
|
+
*
|
|
11
|
+
* The human always sees a result, never a gate.
|
|
12
|
+
* The only terminal failure is an unrecoverable AI error.
|
|
9
13
|
*/
|
|
10
14
|
import { AiAssistClient } from "./aiassist.js";
|
|
15
|
+
import { applyPatch } from "./patch.js";
|
|
16
|
+
// ── Sentinel ──────────────────────────────────────────────────────────────────
|
|
11
17
|
export class Sentinel {
|
|
12
18
|
client;
|
|
13
19
|
constructor(config = {}) {
|
|
14
20
|
this.client = new AiAssistClient({
|
|
15
|
-
apiKey: config.apiKey ??
|
|
21
|
+
apiKey: config.apiKey ??
|
|
22
|
+
process.env["AIASSIST_API_KEY"] ??
|
|
23
|
+
process.env["VITE_AIAS_API_KEY"] ??
|
|
24
|
+
"",
|
|
16
25
|
baseUrl: config.baseUrl,
|
|
17
26
|
model: config.model ?? "gpt-4o",
|
|
18
|
-
timeoutMs: config.timeoutMs ??
|
|
27
|
+
timeoutMs: config.timeoutMs ?? 120_000,
|
|
19
28
|
});
|
|
20
29
|
}
|
|
21
|
-
|
|
30
|
+
// ── 1. Review ───────────────────────────────────────────────────────────────
|
|
22
31
|
async review(opts) {
|
|
23
|
-
const contractStr = JSON.stringify(opts.contract, null, 2);
|
|
24
32
|
const prompt = `You are a code sentinel reviewing an AI-generated patch.
|
|
25
|
-
|
|
33
|
+
Verify the patch is safe, faithful to the app contract, and actually solves the task.
|
|
26
34
|
|
|
27
35
|
App Contract:
|
|
28
36
|
\`\`\`json
|
|
29
|
-
${
|
|
37
|
+
${JSON.stringify(opts.contract, null, 2)}
|
|
30
38
|
\`\`\`
|
|
31
39
|
|
|
32
40
|
Agent task: ${opts.agentTask}
|
|
@@ -43,33 +51,131 @@ ${opts.proposed}
|
|
|
43
51
|
\`\`\`
|
|
44
52
|
|
|
45
53
|
Check for:
|
|
46
|
-
1. Forbidden claims
|
|
47
|
-
2. Forbidden phrases
|
|
48
|
-
3.
|
|
49
|
-
4. Hallucinated data (
|
|
50
|
-
5. Whether the patch
|
|
51
|
-
6.
|
|
54
|
+
1. Forbidden claims (contract.policies.forbiddenClaims)
|
|
55
|
+
2. Forbidden phrases (contract.brand.forbiddenPhrases)
|
|
56
|
+
3. Unauthorised brand color changes
|
|
57
|
+
4. Hallucinated data (numbers, prices, names not in data sources)
|
|
58
|
+
5. Whether the patch actually solves the task
|
|
59
|
+
6. Obvious regressions or broken imports
|
|
52
60
|
|
|
53
|
-
Respond as JSON:
|
|
61
|
+
Respond as JSON only — no markdown fences:
|
|
54
62
|
{
|
|
55
|
-
"approved": true
|
|
56
|
-
"summary": "one sentence
|
|
57
|
-
"violations": [
|
|
58
|
-
"annotations": [
|
|
63
|
+
"approved": true,
|
|
64
|
+
"summary": "one sentence",
|
|
65
|
+
"violations": [],
|
|
66
|
+
"annotations": []
|
|
59
67
|
}`;
|
|
60
|
-
const raw = await this.client.complete(prompt, undefined, {
|
|
68
|
+
const raw = await this.client.complete(prompt, undefined, {
|
|
69
|
+
temperature: 0.1,
|
|
70
|
+
maxTokens: 2_000,
|
|
71
|
+
});
|
|
61
72
|
try {
|
|
62
|
-
const
|
|
63
|
-
return JSON.parse(
|
|
73
|
+
const cleaned = raw.replace(/^```json\s*/i, "").replace(/\s*```$/, "").trim();
|
|
74
|
+
return JSON.parse(cleaned);
|
|
64
75
|
}
|
|
65
76
|
catch {
|
|
66
77
|
return {
|
|
67
78
|
approved: false,
|
|
68
|
-
summary: "Sentinel could not parse its
|
|
79
|
+
summary: "Sentinel could not parse its review — will attempt self-correction.",
|
|
69
80
|
violations: ["Unparseable sentinel response"],
|
|
70
81
|
annotations: [],
|
|
71
82
|
};
|
|
72
83
|
}
|
|
73
84
|
}
|
|
85
|
+
// ── 2. Self-correct ─────────────────────────────────────────────────────────
|
|
86
|
+
async fix(opts) {
|
|
87
|
+
const prompt = `You are a code sentinel. An AI agent generated a patch that has the following issues:
|
|
88
|
+
|
|
89
|
+
${opts.violations.map((v, i) => `${i + 1}. ${v}`).join("\n")}
|
|
90
|
+
|
|
91
|
+
Your job: rewrite the proposed file to fix ALL of these issues while still fulfilling the original task.
|
|
92
|
+
Keep every part of the patch that was correct. Only change what violates the rules.
|
|
93
|
+
|
|
94
|
+
Original task: ${opts.agentTask}
|
|
95
|
+
File: ${opts.filePath}
|
|
96
|
+
|
|
97
|
+
App Contract (source of truth for brand, policies, data):
|
|
98
|
+
\`\`\`json
|
|
99
|
+
${JSON.stringify(opts.contract, null, 2)}
|
|
100
|
+
\`\`\`
|
|
101
|
+
|
|
102
|
+
ORIGINAL FILE:
|
|
103
|
+
\`\`\`tsx
|
|
104
|
+
${opts.original}
|
|
105
|
+
\`\`\`
|
|
106
|
+
|
|
107
|
+
PROPOSED (with violations):
|
|
108
|
+
\`\`\`tsx
|
|
109
|
+
${opts.proposed}
|
|
110
|
+
\`\`\`
|
|
111
|
+
|
|
112
|
+
Respond as JSON only — no markdown fences:
|
|
113
|
+
{
|
|
114
|
+
"correctedContent": "<full corrected file content as a string>",
|
|
115
|
+
"changes": ["Fixed: ...", "Removed: ...", "Replaced: ..."],
|
|
116
|
+
"summary": "one sentence describing what was corrected"
|
|
117
|
+
}`;
|
|
118
|
+
const raw = await this.client.complete(prompt, undefined, {
|
|
119
|
+
temperature: 0.2,
|
|
120
|
+
maxTokens: 8_000,
|
|
121
|
+
});
|
|
122
|
+
try {
|
|
123
|
+
const cleaned = raw.replace(/^```json\s*/i, "").replace(/\s*```$/, "").trim();
|
|
124
|
+
return JSON.parse(cleaned);
|
|
125
|
+
}
|
|
126
|
+
catch {
|
|
127
|
+
// Last resort: return the proposed content unchanged with a note
|
|
128
|
+
return {
|
|
129
|
+
correctedContent: opts.proposed,
|
|
130
|
+
changes: ["Sentinel could not parse its own correction — applied original patch."],
|
|
131
|
+
summary: "Correction parse failed; original patch applied.",
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
// ── 3. Review → fix if needed → apply ──────────────────────────────────────
|
|
136
|
+
/**
|
|
137
|
+
* The primary entry point.
|
|
138
|
+
*
|
|
139
|
+
* Always applies something to disk. Never rejects to the caller.
|
|
140
|
+
* - Clean patch → apply as-is, fix undefined
|
|
141
|
+
* - Dirty patch → self-correct → apply corrected version, fix populated
|
|
142
|
+
*/
|
|
143
|
+
async reviewAndApply(opts) {
|
|
144
|
+
// Step 1 — review
|
|
145
|
+
const review = await this.review({
|
|
146
|
+
contract: opts.contract,
|
|
147
|
+
filePath: opts.patch.file,
|
|
148
|
+
original: opts.patch.original,
|
|
149
|
+
proposed: opts.patch.proposed,
|
|
150
|
+
agentTask: opts.agentTask,
|
|
151
|
+
});
|
|
152
|
+
await opts.store.update(opts.patch.id, {
|
|
153
|
+
sentinelApproved: review.approved,
|
|
154
|
+
sentinelSummary: review.summary,
|
|
155
|
+
sentinelViolations: review.violations,
|
|
156
|
+
});
|
|
157
|
+
// Step 2 — self-correct if needed
|
|
158
|
+
let finalContent = opts.patch.proposed;
|
|
159
|
+
let fix;
|
|
160
|
+
if (!review.approved && review.violations.length > 0) {
|
|
161
|
+
fix = await this.fix({
|
|
162
|
+
contract: opts.contract,
|
|
163
|
+
filePath: opts.patch.file,
|
|
164
|
+
original: opts.patch.original,
|
|
165
|
+
proposed: opts.patch.proposed,
|
|
166
|
+
violations: review.violations,
|
|
167
|
+
agentTask: opts.agentTask,
|
|
168
|
+
});
|
|
169
|
+
finalContent = fix.correctedContent;
|
|
170
|
+
await opts.store.update(opts.patch.id, {
|
|
171
|
+
sentinelSummary: fix.summary,
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
// Step 3 — apply (original or corrected)
|
|
175
|
+
const patchToApply = { ...opts.patch, proposed: finalContent, status: "approved" };
|
|
176
|
+
await opts.store.update(opts.patch.id, { status: "approved", proposed: finalContent });
|
|
177
|
+
await applyPatch(patchToApply, opts.projectRoot, opts.store);
|
|
178
|
+
return { review, applied: true, fix };
|
|
179
|
+
}
|
|
74
180
|
}
|
|
75
181
|
//# sourceMappingURL=sentinel.js.map
|
package/dist/sentinel.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sentinel.js","sourceRoot":"","sources":["../src/sentinel.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"sentinel.js","sourceRoot":"","sources":["../src/sentinel.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@interchained/portal-agent",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "Agent runtime for Portal — AiAssist.net runner/sentinel, audit, generate, improve, guard",
|
|
5
5
|
"license": "GPL-3.0-or-later",
|
|
6
6
|
"author": "Interchained <dev@interchained.org>",
|
|
@@ -18,10 +18,17 @@
|
|
|
18
18
|
"README.md",
|
|
19
19
|
"LICENSE"
|
|
20
20
|
],
|
|
21
|
+
"scripts": {
|
|
22
|
+
"build": "tsc",
|
|
23
|
+
"dev": "tsc --watch",
|
|
24
|
+
"typecheck": "tsc --noEmit",
|
|
25
|
+
"clean": "rm -rf dist",
|
|
26
|
+
"prepublishOnly": "npm run typecheck && npm run build"
|
|
27
|
+
},
|
|
21
28
|
"dependencies": {
|
|
29
|
+
"@interchained/portal-contract": "workspace:*",
|
|
22
30
|
"picocolors": "^1.0.1",
|
|
23
|
-
"ora": "^8.0.1"
|
|
24
|
-
"@interchained/portal-contract": "0.1.0"
|
|
31
|
+
"ora": "^8.0.1"
|
|
25
32
|
},
|
|
26
33
|
"devDependencies": {
|
|
27
34
|
"@types/node": "^20.12.7",
|
|
@@ -32,11 +39,5 @@
|
|
|
32
39
|
},
|
|
33
40
|
"publishConfig": {
|
|
34
41
|
"access": "public"
|
|
35
|
-
},
|
|
36
|
-
"scripts": {
|
|
37
|
-
"build": "tsc",
|
|
38
|
-
"dev": "tsc --watch",
|
|
39
|
-
"typecheck": "tsc --noEmit",
|
|
40
|
-
"clean": "rm -rf dist"
|
|
41
42
|
}
|
|
42
|
-
}
|
|
43
|
+
}
|