合并 master 最新变更
保留共享组件库与 UI 组件复用规范变更 整合 AGC 自主构建与项目工具链最新变更 合并 decision-log 两侧有效决策记录
This commit is contained in:
@@ -362,6 +362,10 @@ export interface AdminEditorAssetPayload {
|
||||
assetKind?: string | null;
|
||||
generationInputs?: Record<string, unknown> | null;
|
||||
sourceResourceId?: string | null;
|
||||
sourceImageSrc?: string | null;
|
||||
sourceObjectKey?: string | null;
|
||||
sourceAssetObjectId?: string | null;
|
||||
sourceLabel?: string | null;
|
||||
thumbnailSrc?: string | null;
|
||||
generationCostMudPoints: number;
|
||||
createdAt: string;
|
||||
@@ -404,6 +408,11 @@ export interface AdminEditorShowcaseAssetPayload {
|
||||
model?: string | null;
|
||||
provider?: string | null;
|
||||
taskId?: string | null;
|
||||
sourceResourceId?: string | null;
|
||||
sourceImageSrc?: string | null;
|
||||
sourceObjectKey?: string | null;
|
||||
sourceAssetObjectId?: string | null;
|
||||
sourceLabel?: string | null;
|
||||
assetKind?: string | null;
|
||||
generationInputs?: Record<string, unknown> | null;
|
||||
thumbnailSrc?: string | null;
|
||||
|
||||
@@ -25,6 +25,11 @@ export interface AdminPreviewableEditorAsset {
|
||||
thumbnailSrc?: string | null;
|
||||
imageSequenceFrames?: AdminEditorImageSequenceFramePayload[] | null;
|
||||
imageSequenceDurationMs?: number | null;
|
||||
sourceResourceId?: string | null;
|
||||
sourceImageSrc?: string | null;
|
||||
sourceObjectKey?: string | null;
|
||||
sourceAssetObjectId?: string | null;
|
||||
sourceLabel?: string | null;
|
||||
}
|
||||
|
||||
export function AdminEditorAssetThumbnail({
|
||||
@@ -70,6 +75,15 @@ export function AdminEditorAssetPreviewDialog({
|
||||
token: string;
|
||||
onClose: () => void;
|
||||
}) {
|
||||
const sourceEntry = entry.sourceImageSrc?.trim()
|
||||
? {
|
||||
assetId: entry.sourceResourceId ?? `${entry.assetId}-source`,
|
||||
label: entry.sourceLabel?.trim() || '原图',
|
||||
imageSrc: entry.sourceImageSrc,
|
||||
objectKey: entry.sourceObjectKey,
|
||||
}
|
||||
: null;
|
||||
|
||||
return (
|
||||
<div className="admin-confirm-backdrop" role="presentation">
|
||||
<section
|
||||
@@ -91,13 +105,19 @@ export function AdminEditorAssetPreviewDialog({
|
||||
<X size={17} aria-hidden="true" />
|
||||
</button>
|
||||
</div>
|
||||
{sourceEntry ? (
|
||||
<div className="admin-asset-query-source-preview">
|
||||
<h4>原图</h4>
|
||||
<AdminEditorAssetPreviewMedia entry={sourceEntry} token={token} />
|
||||
</div>
|
||||
) : null}
|
||||
<AdminEditorAssetPreviewMedia entry={entry} token={token} />
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function AdminEditorAssetPreviewMedia({
|
||||
export function AdminEditorAssetPreviewMedia({
|
||||
entry,
|
||||
token,
|
||||
}: {
|
||||
|
||||
@@ -16,6 +16,7 @@ import type {
|
||||
} from '../api/adminApiTypes';
|
||||
import {
|
||||
AdminEditorAssetPreviewDialog,
|
||||
AdminEditorAssetPreviewMedia,
|
||||
AdminEditorAssetThumbnail,
|
||||
} from '../components/AdminEditorAssetMedia';
|
||||
import { AdminUserReferenceButton } from '../components/AdminUserReferenceButton';
|
||||
@@ -561,14 +562,34 @@ function AdminAssetDetailDialog({
|
||||
</button>
|
||||
</div>
|
||||
<div className="admin-asset-query-detail-layout">
|
||||
<button
|
||||
className="admin-asset-query-thumb-button admin-asset-query-detail-thumb-button"
|
||||
title="预览素材"
|
||||
type="button"
|
||||
onClick={() => onPreview(entry)}
|
||||
>
|
||||
<AdminEditorAssetThumbnail entry={entry} token={token} />
|
||||
</button>
|
||||
<div className="admin-asset-query-detail-media">
|
||||
{entry.sourceImageSrc?.trim() ? (
|
||||
<div className="admin-asset-query-detail-media-card">
|
||||
<h4>原图</h4>
|
||||
<AdminEditorAssetPreviewMedia
|
||||
entry={{
|
||||
assetId:
|
||||
entry.sourceResourceId ?? `${entry.assetId}-source`,
|
||||
label: entry.sourceLabel?.trim() || '原图',
|
||||
imageSrc: entry.sourceImageSrc,
|
||||
objectKey: entry.sourceObjectKey,
|
||||
}}
|
||||
token={token}
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
<div className="admin-asset-query-detail-media-card">
|
||||
<h4>当前结果</h4>
|
||||
<button
|
||||
className="admin-asset-query-thumb-button admin-asset-query-detail-thumb-button"
|
||||
title="预览素材"
|
||||
type="button"
|
||||
onClick={() => onPreview(entry)}
|
||||
>
|
||||
<AdminEditorAssetThumbnail entry={entry} token={token} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<dl className="admin-info-list admin-detail-list">
|
||||
<AdminInfoItem label="作者">
|
||||
<div className="admin-inline-identity">
|
||||
|
||||
@@ -17,6 +17,7 @@ import type {
|
||||
} from '../api/adminApiTypes';
|
||||
import {
|
||||
AdminEditorAssetPreviewDialog,
|
||||
AdminEditorAssetPreviewMedia,
|
||||
AdminEditorAssetThumbnail,
|
||||
} from '../components/AdminEditorAssetMedia';
|
||||
import { AdminUserReferenceButton } from '../components/AdminUserReferenceButton';
|
||||
@@ -709,18 +710,38 @@ function AdminShowcaseDetailDialog({
|
||||
</button>
|
||||
</div>
|
||||
<div className="admin-asset-query-detail-layout">
|
||||
<button
|
||||
className="admin-asset-query-thumb-button admin-asset-query-detail-thumb-button"
|
||||
title="预览素材"
|
||||
type="button"
|
||||
onClick={() => onPreview(entry)}
|
||||
>
|
||||
<AdminEditorAssetThumbnail
|
||||
entry={entry}
|
||||
token={token}
|
||||
altPrefix="精选素材"
|
||||
/>
|
||||
</button>
|
||||
<div className="admin-asset-query-detail-media">
|
||||
{entry.sourceImageSrc?.trim() ? (
|
||||
<div className="admin-asset-query-detail-media-card">
|
||||
<h4>原图</h4>
|
||||
<AdminEditorAssetPreviewMedia
|
||||
entry={{
|
||||
assetId:
|
||||
entry.sourceResourceId ?? `${entry.assetId}-source`,
|
||||
label: entry.sourceLabel?.trim() || '原图',
|
||||
imageSrc: entry.sourceImageSrc,
|
||||
objectKey: entry.sourceObjectKey,
|
||||
}}
|
||||
token={token}
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
<div className="admin-asset-query-detail-media-card">
|
||||
<h4>当前结果</h4>
|
||||
<button
|
||||
className="admin-asset-query-thumb-button admin-asset-query-detail-thumb-button"
|
||||
title="预览素材"
|
||||
type="button"
|
||||
onClick={() => onPreview(entry)}
|
||||
>
|
||||
<AdminEditorAssetThumbnail
|
||||
entry={entry}
|
||||
token={token}
|
||||
altPrefix="精选素材"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<dl className="admin-info-list admin-detail-list">
|
||||
<AdminInfoItem label="作者">
|
||||
<div className="admin-inline-identity">
|
||||
|
||||
@@ -1655,6 +1655,18 @@ button:disabled {
|
||||
width: min(100%, 860px);
|
||||
}
|
||||
|
||||
.admin-asset-query-source-preview {
|
||||
margin: 0 20px 16px;
|
||||
padding-bottom: 16px;
|
||||
border-bottom: 1px solid #e2e8f0;
|
||||
}
|
||||
|
||||
.admin-asset-query-source-preview h4 {
|
||||
margin: 0 0 8px;
|
||||
color: #64748b;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.admin-asset-query-prompt-dialog .admin-panel-heading > div,
|
||||
.admin-asset-query-detail-dialog .admin-panel-heading > div,
|
||||
.admin-asset-query-preview-dialog .admin-panel-heading > div {
|
||||
@@ -1683,6 +1695,27 @@ button:disabled {
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
.admin-asset-query-detail-media {
|
||||
display: grid;
|
||||
gap: 14px;
|
||||
}
|
||||
|
||||
.admin-asset-query-detail-media-card {
|
||||
display: grid;
|
||||
gap: 7px;
|
||||
}
|
||||
|
||||
.admin-asset-query-detail-media-card h4 {
|
||||
margin: 0;
|
||||
color: #64748b;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.admin-asset-query-detail-media-card .admin-asset-query-preview-media {
|
||||
width: 220px;
|
||||
height: 220px;
|
||||
}
|
||||
|
||||
.admin-asset-query-detail-thumb-button .admin-asset-query-thumb {
|
||||
width: 220px;
|
||||
height: 220px;
|
||||
@@ -2332,6 +2365,10 @@ button:disabled {
|
||||
justify-self: center;
|
||||
}
|
||||
|
||||
.admin-asset-query-detail-media {
|
||||
justify-items: center;
|
||||
}
|
||||
|
||||
.admin-dashboard-tabs {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
{
|
||||
"name": "@genarrative/ai-game-creator-shell",
|
||||
"private": true,
|
||||
"version": "0.1.8",
|
||||
"version": "0.1.12",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "node scripts/start-tauri-dev.mjs",
|
||||
"dev-server": "node scripts/start-dev-server.mjs",
|
||||
"dev-stack": "node scripts/start-dev-stack.mjs",
|
||||
"build": "npm --prefix ../.. exec tauri -- build",
|
||||
"skill-pack:check": "node scripts/check-skill-pack.mjs",
|
||||
"skill-pack:sync": "node scripts/check-skill-pack.mjs --write",
|
||||
"skill-pack:test": "node --test scripts/check-skill-pack.test.mjs",
|
||||
"llm-status": "node scripts/run-cli-with-config.mjs --llm-status",
|
||||
"agent-task": "node scripts/run-cli-with-config.mjs --agent-task",
|
||||
"chat": "node scripts/run-cli-with-config.mjs --swarm-chat",
|
||||
@@ -31,7 +34,7 @@
|
||||
"agent-runtime:supervisor-swarm-tool-plan-handoff-runner-kill-real-e2e": "node scripts/agent-runtime-real-e2e.mjs --suite supervisor-swarm-tool-plan-handoff-runner-kill",
|
||||
"agent-runtime:steer-real-e2e": "node scripts/agent-runtime-steer-real-e2e.mjs",
|
||||
"agent-runtime:steer-runner-kill-real-e2e": "node scripts/agent-runtime-real-e2e.mjs --suite steer-runner-kill",
|
||||
"typecheck": "tsc -p tsconfig.json --noEmit && node scripts/check-config.mjs"
|
||||
"typecheck": "tsc -p tsconfig.json --noEmit && npm run skill-pack:check && node scripts/check-config.mjs"
|
||||
},
|
||||
"dependencies": {
|
||||
"@cubone/react-file-manager": "^1.35.0",
|
||||
|
||||
@@ -26,6 +26,10 @@ const wrapperSuite =
|
||||
const configFileName = 'game-creator.config.json';
|
||||
const configSentinelName = '.deterministic-provider-e2e.json';
|
||||
const configSentinelSchema = 'genarrative-deterministic-provider-e2e-config.v1';
|
||||
const platformSessionFixtureEnv = 'GENARRATIVE_AGC_PLATFORM_SESSION_FIXTURE';
|
||||
const platformSessionFixtureName = '.deterministic-platform-session.json';
|
||||
const platformSessionFixtureSchema =
|
||||
'genarrative-agc-platform-session-fixture.v1';
|
||||
const outputLimit = 32 * 1024 * 1024;
|
||||
|
||||
function hashValue(value) {
|
||||
@@ -77,6 +81,16 @@ function deterministicRuntimeConfig(apiKey, provider) {
|
||||
};
|
||||
}
|
||||
|
||||
function deterministicPlatformSessionFixture(apiKey, provider) {
|
||||
return {
|
||||
schemaVersion: platformSessionFixtureSchema,
|
||||
userId: 'deterministic-e2e-user',
|
||||
accessToken: apiKey,
|
||||
apiBaseUrl: provider.editorBaseUrl,
|
||||
generation: 1,
|
||||
};
|
||||
}
|
||||
|
||||
function appendBounded(current, chunk) {
|
||||
const combined = Buffer.concat([current, chunk]);
|
||||
if (combined.length > outputLimit) throw new Error('child-output-too-large');
|
||||
@@ -1909,6 +1923,12 @@ async function runE2e(options) {
|
||||
);
|
||||
provider = await startDeterministicLaneDefenseProvider({ apiKey });
|
||||
const config = deterministicRuntimeConfig(apiKey, provider);
|
||||
const fixturePath = path.join(configDir, platformSessionFixtureName);
|
||||
await fs.writeFile(
|
||||
fixturePath,
|
||||
`${JSON.stringify(deterministicPlatformSessionFixture(apiKey, provider))}\n`,
|
||||
{ flag: 'wx', mode: 0o600 },
|
||||
);
|
||||
await fs.writeFile(
|
||||
path.join(configDir, configFileName),
|
||||
`${JSON.stringify(config)}\n`,
|
||||
@@ -1924,7 +1944,11 @@ async function runE2e(options) {
|
||||
if (options.keepProject) childArgs.push('--keep-project');
|
||||
childResult = await runChild(
|
||||
childArgs,
|
||||
withLoopbackNoProxy({ ...process.env, NO_COLOR: '1' }),
|
||||
withLoopbackNoProxy({
|
||||
...process.env,
|
||||
NO_COLOR: '1',
|
||||
[platformSessionFixtureEnv]: fixturePath,
|
||||
}),
|
||||
);
|
||||
childReport = parseChildReport(childResult);
|
||||
} catch (error) {
|
||||
|
||||
@@ -100,6 +100,13 @@ import { appendBounded, runProcess } from './process.mjs';
|
||||
import { decodeUtf8Fatal, isIsolatedRunnerSuite } from './reporting.mjs';
|
||||
import { killRunnerOnce, readRunnerStatus, runnerBootId } from './runtime.mjs';
|
||||
|
||||
const platformSessionFixtureEnv = 'GENARRATIVE_AGC_PLATFORM_SESSION_FIXTURE';
|
||||
const platformSessionFixtureMaxBytes = 16 * 1024;
|
||||
const isolatedPlatformSessionFixtureName =
|
||||
'.deterministic-platform-session.json';
|
||||
const platformSessionFixtureSchema =
|
||||
'genarrative-agc-platform-session-fixture.v1';
|
||||
|
||||
export function isolatedSuiteProtectsSourceAppData() {
|
||||
return (
|
||||
isSupervisorSwarmTransientRetrySuite() ||
|
||||
@@ -498,6 +505,120 @@ export async function verifySourceAppDataDirectoryUntouched() {
|
||||
state.isolatedRunner.sourceAppDataDirectoryUntouched = true;
|
||||
}
|
||||
|
||||
async function readPlatformSessionFixtureForIsolatedSuite(sourceConfigDir) {
|
||||
const rawPath = process.env[platformSessionFixtureEnv];
|
||||
assert(
|
||||
isNonEmptyString(rawPath) && path.isAbsolute(rawPath),
|
||||
'supervisor-autonomous-playable-platform-session-fixture-missing',
|
||||
);
|
||||
const sourceRealPath = await fs.realpath(sourceConfigDir);
|
||||
const requestedPath = path.resolve(rawPath);
|
||||
const requestedMetadata = await fs.lstat(requestedPath).catch((error) => {
|
||||
if (error?.code === 'ENOENT') return null;
|
||||
throw error;
|
||||
});
|
||||
assert(
|
||||
requestedMetadata?.isFile() && !requestedMetadata.isSymbolicLink(),
|
||||
'supervisor-autonomous-playable-platform-session-fixture-not-regular',
|
||||
);
|
||||
assert(
|
||||
requestedMetadata.size <= platformSessionFixtureMaxBytes,
|
||||
'supervisor-autonomous-playable-platform-session-fixture-too-large',
|
||||
);
|
||||
const realPath = await fs.realpath(requestedPath);
|
||||
assert(
|
||||
isPathInside(sourceRealPath, realPath),
|
||||
'supervisor-autonomous-playable-platform-session-fixture-outside-config',
|
||||
);
|
||||
const bytes = await fs.readFile(realPath);
|
||||
assert(
|
||||
bytes.length <= platformSessionFixtureMaxBytes,
|
||||
'supervisor-autonomous-playable-platform-session-fixture-too-large',
|
||||
);
|
||||
let fixture;
|
||||
try {
|
||||
fixture = JSON.parse(
|
||||
decodeUtf8Fatal(bytes, 'platform-session-fixture-invalid-utf8'),
|
||||
);
|
||||
} catch (error) {
|
||||
throw codedError(
|
||||
'supervisor-autonomous-playable-platform-session-fixture-invalid',
|
||||
error,
|
||||
);
|
||||
}
|
||||
const expectedKeys = [
|
||||
'schemaVersion',
|
||||
'userId',
|
||||
'accessToken',
|
||||
'apiBaseUrl',
|
||||
'generation',
|
||||
];
|
||||
assert(
|
||||
isPlainObject(fixture) &&
|
||||
JSON.stringify(Object.keys(fixture).sort()) ===
|
||||
JSON.stringify([...expectedKeys].sort()) &&
|
||||
fixture.schemaVersion === platformSessionFixtureSchema &&
|
||||
isNonEmptyString(fixture.userId) &&
|
||||
isNonEmptyString(fixture.accessToken) &&
|
||||
isNonEmptyString(fixture.apiBaseUrl) &&
|
||||
Number.isSafeInteger(fixture.generation) &&
|
||||
fixture.generation > 0,
|
||||
'supervisor-autonomous-playable-platform-session-fixture-invalid',
|
||||
);
|
||||
return {
|
||||
sourcePath: realPath,
|
||||
bytes,
|
||||
fixture,
|
||||
sha256: createHash('sha256').update(bytes).digest('hex'),
|
||||
};
|
||||
}
|
||||
|
||||
async function installPlatformSessionFixtureIntoIsolatedAppData(
|
||||
sourceConfigDir,
|
||||
appDataDir,
|
||||
) {
|
||||
if (!isSupervisorAutonomousPlayableLaneDefenseSuite()) return;
|
||||
const source =
|
||||
await readPlatformSessionFixtureForIsolatedSuite(sourceConfigDir);
|
||||
const isolatedPath = path.join(
|
||||
appDataDir,
|
||||
isolatedPlatformSessionFixtureName,
|
||||
);
|
||||
await fs.copyFile(
|
||||
source.sourcePath,
|
||||
isolatedPath,
|
||||
fsConstants.COPYFILE_EXCL | fsConstants.COPYFILE_FICLONE,
|
||||
);
|
||||
await fs.chmod(isolatedPath, 0o600).catch(() => {});
|
||||
const isolatedMetadata = await fs.lstat(isolatedPath);
|
||||
assert(
|
||||
isolatedMetadata.isFile() &&
|
||||
!isolatedMetadata.isSymbolicLink() &&
|
||||
isolatedMetadata.size === source.bytes.length,
|
||||
'supervisor-autonomous-playable-platform-session-fixture-copy-invalid',
|
||||
);
|
||||
const isolatedBytes = await fs.readFile(isolatedPath);
|
||||
assert(
|
||||
createHash('sha256').update(isolatedBytes).digest('hex') === source.sha256,
|
||||
'supervisor-autonomous-playable-platform-session-fixture-copy-mismatch',
|
||||
);
|
||||
state.isolatedRunner.platformSessionFixtureSourcePath = source.sourcePath;
|
||||
state.isolatedRunner.platformSessionFixturePath = isolatedPath;
|
||||
state.isolatedRunner.platformSessionFixtureSha256 = source.sha256;
|
||||
state.isolatedRunner.platformSessionFixturePreviousEnv =
|
||||
Object.prototype.hasOwnProperty.call(process.env, platformSessionFixtureEnv)
|
||||
? process.env[platformSessionFixtureEnv]
|
||||
: undefined;
|
||||
process.env[platformSessionFixtureEnv] = isolatedPath;
|
||||
state.formalConfigPathTranscriptScanner?.addSecrets(
|
||||
absolutePathVariants(source.sourcePath, isolatedPath),
|
||||
);
|
||||
const previousLeakCount = state.transcriptScanner?.count ?? 0;
|
||||
state.secrets = [...new Set([...state.secrets, source.fixture.accessToken])];
|
||||
rebuildSupervisorSwarmTranscriptScanner();
|
||||
state.transcriptScanner.count = previousLeakCount;
|
||||
}
|
||||
|
||||
export async function prepareIsolatedSuiteAppData({
|
||||
streamAgentId = null,
|
||||
webSearchAgentId = null,
|
||||
@@ -769,6 +890,10 @@ export async function prepareIsolatedSuiteAppData({
|
||||
state.secrets = [...suiteSecrets];
|
||||
rebuildSupervisorSwarmTranscriptScanner();
|
||||
state.transcriptScanner.count = previousLeakCount;
|
||||
await installPlatformSessionFixtureIntoIsolatedAppData(
|
||||
sourceConfigDir,
|
||||
appDataDir,
|
||||
);
|
||||
const unexpectedEndpoint = await fs
|
||||
.lstat(path.join(appDataDir, runnerEndpointFileName))
|
||||
.catch((error) => {
|
||||
@@ -1874,6 +1999,31 @@ export async function verifyIsolatedSuiteConfigLinksUnchanged() {
|
||||
'isolated-source-config-changed-during-suite',
|
||||
);
|
||||
}
|
||||
const fixture = state.isolatedRunner;
|
||||
if (
|
||||
fixture.platformSessionFixtureSourcePath &&
|
||||
fixture.platformSessionFixturePath &&
|
||||
fixture.platformSessionFixtureSha256
|
||||
) {
|
||||
const [sourceMetadata, isolatedMetadata, sourceBytes, isolatedBytes] =
|
||||
await Promise.all([
|
||||
fs.lstat(fixture.platformSessionFixtureSourcePath),
|
||||
fs.lstat(fixture.platformSessionFixturePath),
|
||||
fs.readFile(fixture.platformSessionFixtureSourcePath),
|
||||
fs.readFile(fixture.platformSessionFixturePath),
|
||||
]);
|
||||
assert(
|
||||
sourceMetadata.isFile() &&
|
||||
!sourceMetadata.isSymbolicLink() &&
|
||||
isolatedMetadata.isFile() &&
|
||||
!isolatedMetadata.isSymbolicLink() &&
|
||||
createHash('sha256').update(sourceBytes).digest('hex') ===
|
||||
fixture.platformSessionFixtureSha256 &&
|
||||
createHash('sha256').update(isolatedBytes).digest('hex') ===
|
||||
fixture.platformSessionFixtureSha256,
|
||||
'supervisor-autonomous-playable-platform-session-fixture-changed',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export async function verifySourceConfigLinkCountsRestored() {
|
||||
@@ -1913,7 +2063,22 @@ export async function removeIsolatedSuiteAppData() {
|
||||
} catch (error) {
|
||||
ownershipError = error;
|
||||
}
|
||||
await fs.rm(appDataDir, { recursive: true, force: false });
|
||||
try {
|
||||
await fs.rm(appDataDir, { recursive: true, force: false });
|
||||
} finally {
|
||||
if (state.isolatedRunner.platformSessionFixtureSourcePath) {
|
||||
const previous = state.isolatedRunner.platformSessionFixturePreviousEnv;
|
||||
if (previous === undefined) {
|
||||
delete process.env[platformSessionFixtureEnv];
|
||||
} else {
|
||||
process.env[platformSessionFixtureEnv] = previous;
|
||||
}
|
||||
}
|
||||
state.isolatedRunner.platformSessionFixturePath = null;
|
||||
state.isolatedRunner.platformSessionFixtureSourcePath = null;
|
||||
state.isolatedRunner.platformSessionFixtureSha256 = null;
|
||||
state.isolatedRunner.platformSessionFixturePreviousEnv = undefined;
|
||||
}
|
||||
state.runtimeConfigDir = state.options.configDir;
|
||||
try {
|
||||
await verifySourceConfigLinkCountsRestored();
|
||||
|
||||
@@ -84,6 +84,14 @@ export function buildCliChildEnvironment() {
|
||||
NO_COLOR: '1',
|
||||
RUST_BACKTRACE: '0',
|
||||
};
|
||||
// The deterministic playable suite copies its account fixture into the
|
||||
// sibling isolated AppData directory. Set the path explicitly here so
|
||||
// every CLI and the Runner it launches use the isolated copy, even if the
|
||||
// parent harness environment was restored or changed after setup.
|
||||
if (state.isolatedRunner.platformSessionFixturePath) {
|
||||
environment.GENARRATIVE_AGC_PLATFORM_SESSION_FIXTURE =
|
||||
state.isolatedRunner.platformSessionFixturePath;
|
||||
}
|
||||
return isSupervisorSwarmTransientRetrySuite()
|
||||
? withLoopbackNoProxy(environment)
|
||||
: environment;
|
||||
|
||||
@@ -241,7 +241,7 @@ export const autonomousCompletionContractSchemaVersion =
|
||||
'game-creator-autonomous-completion-contract.v2';
|
||||
|
||||
export const autonomousPlaytestReceiptSchemaVersion =
|
||||
'game-creator-autonomous-playtest-receipt.v1';
|
||||
'game-creator-autonomous-playtest-receipt.v2';
|
||||
|
||||
export const autonomousGameBuildRunProfile = 'autonomous-game-build';
|
||||
|
||||
@@ -938,6 +938,10 @@ export class BlockedError extends Error {
|
||||
|
||||
export const isolatedRunnerState = {
|
||||
appDataDir: null,
|
||||
platformSessionFixturePath: null,
|
||||
platformSessionFixtureSourcePath: null,
|
||||
platformSessionFixtureSha256: null,
|
||||
platformSessionFixturePreviousEnv: undefined,
|
||||
ownerToken: null,
|
||||
createdAt: 0,
|
||||
current: null,
|
||||
|
||||
+5
@@ -1220,6 +1220,11 @@ export async function validateSupervisorAutonomousPlayableEvidence(
|
||||
agentId: receipt.agentId,
|
||||
runId: receipt.runId,
|
||||
runProfileBindingFingerprint: receipt.runProfileBindingFingerprint,
|
||||
executorAgentId: receipt.executorAgentId,
|
||||
executorRunId: receipt.executorRunId,
|
||||
executorSource: receipt.executorSource,
|
||||
executorRunProfileBindingFingerprint:
|
||||
receipt.executorRunProfileBindingFingerprint,
|
||||
actionId: receipt.actionId,
|
||||
actionFingerprint: receipt.actionFingerprint,
|
||||
revision: receipt.revision,
|
||||
|
||||
@@ -143,7 +143,12 @@ export const usage = `用法:
|
||||
--plan 走「做方案」立项策划入口,不做游戏,不做产物验收和试玩
|
||||
--timeout-minutes <分钟> 设置本次执行期限;自动任务默认 50 分钟,--plan 默认 6 分钟,手工模式默认不限时
|
||||
--dry-run 只检查目录发现和项目准备,不启动 LLM
|
||||
-h, --help 显示帮助`;
|
||||
-h, --help 显示帮助
|
||||
|
||||
环境变量:
|
||||
AGC_PLAN_GDD_DECISION 审批卡自动应答动作,默认 approve;revise/reject 必须
|
||||
同时用 AGC_PLAN_GDD_COMMENT 给出真实修改意见
|
||||
AGC_PLAN_GDD_COMMENT revise/reject 的意见原文`;
|
||||
|
||||
function readOptionValue(args, index, option) {
|
||||
const value = args[index + 1]?.trim();
|
||||
@@ -911,12 +916,15 @@ export function parseSettledSwarmTurnReport(output) {
|
||||
async function runCapturedCargo(
|
||||
cliArguments,
|
||||
setActiveChild,
|
||||
{ timeoutMs = null, label = 'Cargo 子命令' } = {},
|
||||
{ timeoutMs = null, label = 'Cargo 子命令', stdin = null } = {},
|
||||
) {
|
||||
const child = spawnChild(cargoCommand, buildCargoCliArguments(cliArguments), {
|
||||
stdio: ['ignore', 'pipe', 'pipe'],
|
||||
stdio: [stdin === null ? 'ignore' : 'pipe', 'pipe', 'pipe'],
|
||||
});
|
||||
setActiveChild(child);
|
||||
if (stdin !== null) {
|
||||
child.stdin.end(stdin);
|
||||
}
|
||||
let stdout = '';
|
||||
let stderr = '';
|
||||
child.stdout.setEncoding('utf8');
|
||||
@@ -1007,7 +1015,9 @@ async function runTaskCargo(
|
||||
let planGddApprovalPromise = null;
|
||||
const startPlanGddApproval = () => {
|
||||
planGddApprovalStarted = true;
|
||||
console.log('[自动审批] 检测到 Fast GDD 审批位,正在提交 approve');
|
||||
console.log(
|
||||
`[自动审批] 检测到 Fast GDD 审批位,正在提交 ${resolvePlanGddAutoDecision().action}`,
|
||||
);
|
||||
planGddApprovalPromise = onPlanGddApprovalWait()
|
||||
.then((value) => {
|
||||
planGddApproval = value;
|
||||
@@ -1842,13 +1852,30 @@ export function parsePlanGddDecisionOutput(output) {
|
||||
);
|
||||
}
|
||||
|
||||
// 审批卡是这条链路唯一的人类判据,所以自动应答只投 approve,且只在投影确实有一张
|
||||
// 待决定审批时出手。revise/reject 需要一段真实的修改意见,让机器编一段等于把判据
|
||||
// 换成噪声;要跑那两条分支就手工调 --plan-gdd-decide。
|
||||
// 审批卡是这条链路唯一的人类判据,所以自动应答默认只投 approve,且只在投影确实有
|
||||
// 一张待决定审批时出手。revise/reject 需要一段真实的修改意见,让机器编一段等于把
|
||||
// 判据换成噪声——所以那两条分支只在跑的人自己用 AGC_PLAN_GDD_COMMENT 给出意见时
|
||||
// 才走。手工调 --plan-gdd-decide 也能达到同样效果,但那要求 plan 根 run 仍然活着,
|
||||
// 而它恰好是本进程持有的 CLI 子进程。
|
||||
export function planGddAutoApprovalIsPending(state) {
|
||||
return Boolean(state?.pendingApproval);
|
||||
}
|
||||
|
||||
export function resolvePlanGddAutoDecision(env = process.env) {
|
||||
const action = (env.AGC_PLAN_GDD_DECISION ?? 'approve').trim();
|
||||
if (!['approve', 'revise', 'reject'].includes(action)) {
|
||||
throw new Error('AGC_PLAN_GDD_DECISION 只能是 approve / revise / reject');
|
||||
}
|
||||
const comment = (env.AGC_PLAN_GDD_COMMENT ?? '').trim();
|
||||
if (action === 'approve') return { action, comment: null };
|
||||
if (!comment) {
|
||||
throw new Error(
|
||||
`${action} 必须同时设 AGC_PLAN_GDD_COMMENT 提供真实修改意见`,
|
||||
);
|
||||
}
|
||||
return { action, comment };
|
||||
}
|
||||
|
||||
async function settlePlanGddApproval(
|
||||
projectPath,
|
||||
runtimeConfigPath,
|
||||
@@ -1875,18 +1902,21 @@ async function settlePlanGddApproval(
|
||||
if (!planGddAutoApprovalIsPending(before)) {
|
||||
return { decided: false, state: before };
|
||||
}
|
||||
const { action, comment } = resolvePlanGddAutoDecision();
|
||||
const decision = await runCapturedCargo(
|
||||
[
|
||||
'--config-dir',
|
||||
runtimeConfigPath,
|
||||
'--plan-gdd-decide',
|
||||
projectPath,
|
||||
'approve',
|
||||
action,
|
||||
...(comment === null ? [] : ['--stdin']),
|
||||
],
|
||||
setActiveChild,
|
||||
{
|
||||
timeoutMs: planGddApprovalTimeoutMs,
|
||||
label: 'Fast GDD 审批决定',
|
||||
stdin: comment,
|
||||
},
|
||||
);
|
||||
if (decision.code !== 0 || decision.signal) {
|
||||
@@ -1926,7 +1956,7 @@ async function reportPlanGddApproval(approval) {
|
||||
return;
|
||||
}
|
||||
console.log(
|
||||
` [已批准] outcome=${approval.receipt.outcome} v${approval.receipt.decisionRef.version} 投影状态=${state.state}`,
|
||||
` [已决定 ${approval.receipt.decisionRef.action}] outcome=${approval.receipt.outcome} v${approval.receipt.decisionRef.version} 投影状态=${state.state}`,
|
||||
);
|
||||
if (approval.recovered) {
|
||||
console.log(
|
||||
|
||||
@@ -1533,11 +1533,13 @@ if (
|
||||
}
|
||||
|
||||
if (
|
||||
tauriConfig.version !== '0.1.8' ||
|
||||
packageConfig.version !== '0.1.8' ||
|
||||
cargoPackageVersion !== '0.1.8'
|
||||
tauriConfig.version !== '0.1.12' ||
|
||||
packageConfig.version !== '0.1.12' ||
|
||||
cargoPackageVersion !== '0.1.12'
|
||||
) {
|
||||
throw new Error('AI game creator standard release must remain version 0.1.8');
|
||||
throw new Error(
|
||||
'AI game creator standard release must remain version 0.1.12',
|
||||
);
|
||||
}
|
||||
|
||||
const devServerSource = fs.readFileSync(
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
import process from 'node:process';
|
||||
|
||||
import {
|
||||
inspectSkillPack,
|
||||
syncSkillPackManifest,
|
||||
} from './skill-pack-manifest.mjs';
|
||||
|
||||
const argumentsList = process.argv.slice(2);
|
||||
const writeMode = argumentsList.length === 1 && argumentsList[0] === '--write';
|
||||
if (
|
||||
argumentsList.length > 1 ||
|
||||
(argumentsList.length === 1 && argumentsList[0] !== '--write')
|
||||
) {
|
||||
console.error('用法:node scripts/check-skill-pack.mjs [--write]');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
try {
|
||||
if (writeMode) {
|
||||
const result = syncSkillPackManifest();
|
||||
if (!result.changed) {
|
||||
console.log(`[skill-pack] 已是最新(version=${result.version})`);
|
||||
} else {
|
||||
console.log(
|
||||
`[skill-pack] 已同步 ${result.mismatches.map((item) => item.name).join('、')}(version=${result.version})`,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
const result = inspectSkillPack();
|
||||
if (result.mismatches.length > 0) {
|
||||
console.error('[skill-pack] 内容指纹与 manifest 不一致:');
|
||||
for (const mismatch of result.mismatches) {
|
||||
console.error(
|
||||
`- ${mismatch.name}: manifest=${mismatch.expected} actual=${mismatch.actual}`,
|
||||
);
|
||||
}
|
||||
console.error('[skill-pack] 内容变更后运行:npm run agc:skill-pack:sync');
|
||||
process.exitCode = 1;
|
||||
} else {
|
||||
console.log(`[skill-pack] OK(version=${result.manifest.version})`);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(
|
||||
`[skill-pack] ${error instanceof Error ? error.message : String(error)}`,
|
||||
);
|
||||
process.exitCode = 1;
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import { spawnSync } from 'node:child_process';
|
||||
import fs from 'node:fs';
|
||||
import os from 'node:os';
|
||||
import path from 'node:path';
|
||||
import test from 'node:test';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
import {
|
||||
computeSkillContentFingerprint,
|
||||
inspectSkillPack,
|
||||
} from './skill-pack-manifest.mjs';
|
||||
|
||||
test('bundled skill pack manifest is synchronized', () => {
|
||||
const result = inspectSkillPack();
|
||||
assert.deepEqual(result.mismatches, []);
|
||||
});
|
||||
|
||||
test('skill content fingerprint canonicalizes CRLF', () => {
|
||||
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'agc-skill-pack-'));
|
||||
try {
|
||||
fs.mkdirSync(path.join(root, 'demo'), { recursive: true });
|
||||
const entry = {
|
||||
name: 'demo',
|
||||
files: ['SKILL.md'],
|
||||
};
|
||||
fs.writeFileSync(path.join(root, 'demo', 'SKILL.md'), 'line 1\nline 2\n');
|
||||
const lf = computeSkillContentFingerprint(root, entry);
|
||||
fs.writeFileSync(
|
||||
path.join(root, 'demo', 'SKILL.md'),
|
||||
'line 1\r\nline 2\r\n',
|
||||
);
|
||||
assert.equal(computeSkillContentFingerprint(root, entry), lf);
|
||||
} finally {
|
||||
fs.rmSync(root, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test('check command without arguments remains read-only', () => {
|
||||
const scriptPath = path.join(
|
||||
path.dirname(fileURLToPath(import.meta.url)),
|
||||
'check-skill-pack.mjs',
|
||||
);
|
||||
const result = spawnSync(process.execPath, [scriptPath], {
|
||||
encoding: 'utf8',
|
||||
});
|
||||
assert.equal(result.status, 0, result.stderr);
|
||||
assert.match(result.stdout, /\[skill-pack\] OK/u);
|
||||
});
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,213 @@
|
||||
import crypto from 'node:crypto';
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { TextDecoder } from 'node:util';
|
||||
|
||||
export const SKILL_PACK_SCHEMA_VERSION = 'agc-skill-pack.v1';
|
||||
export const EXPECTED_SKILL_NAMES = Object.freeze([
|
||||
'agc-browser-playtest',
|
||||
'agc-client-projection',
|
||||
'agc-project-structure',
|
||||
'agc-web-game-development',
|
||||
'taonier-art-assets',
|
||||
]);
|
||||
|
||||
const utf8Decoder = new TextDecoder('utf-8', { fatal: true });
|
||||
const defaultRoot = path.resolve(
|
||||
path.dirname(fileURLToPath(import.meta.url)),
|
||||
'../src-tauri/resources/agc-skills',
|
||||
);
|
||||
|
||||
function canonicalTextBytes(filePath) {
|
||||
const decoded = utf8Decoder.decode(fs.readFileSync(filePath));
|
||||
return Buffer.from(decoded.replaceAll('\r\n', '\n'), 'utf8');
|
||||
}
|
||||
|
||||
export function isSafeSkillRelativePath(value) {
|
||||
if (
|
||||
typeof value !== 'string' ||
|
||||
value.length === 0 ||
|
||||
value.includes('\\') ||
|
||||
value.includes(':') ||
|
||||
value.startsWith('/')
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
return value
|
||||
.split('/')
|
||||
.every(
|
||||
(segment) => segment.length > 0 && segment !== '.' && segment !== '..',
|
||||
);
|
||||
}
|
||||
|
||||
function skillFilePath(rootDir, skillName, relativePath) {
|
||||
if (!isSafeSkillRelativePath(relativePath)) {
|
||||
throw new Error(`Skill ${skillName} 包含不安全相对路径: ${relativePath}`);
|
||||
}
|
||||
const target = path.resolve(rootDir, skillName, ...relativePath.split('/'));
|
||||
const skillRoot = path.resolve(rootDir, skillName);
|
||||
const prefix = `${skillRoot}${path.sep}`;
|
||||
if (!target.startsWith(prefix)) {
|
||||
throw new Error(`Skill ${skillName} 路径越过审核根目录: ${relativePath}`);
|
||||
}
|
||||
return target;
|
||||
}
|
||||
|
||||
export function computeSkillContentFingerprint(rootDir, entry) {
|
||||
const digest = crypto.createHash('sha256');
|
||||
for (const relativePath of [...entry.files].sort()) {
|
||||
const filePath = skillFilePath(rootDir, entry.name, relativePath);
|
||||
const bytes = canonicalTextBytes(filePath);
|
||||
digest.update(relativePath, 'utf8');
|
||||
digest.update(Buffer.from([0]));
|
||||
digest.update(bytes);
|
||||
digest.update(Buffer.from([0]));
|
||||
}
|
||||
return digest.digest('hex');
|
||||
}
|
||||
|
||||
function collectBundledFiles(rootDir) {
|
||||
const files = [];
|
||||
const walk = (directory, prefix) => {
|
||||
for (const entry of fs.readdirSync(directory, { withFileTypes: true })) {
|
||||
const relativePath = prefix ? `${prefix}/${entry.name}` : entry.name;
|
||||
const absolutePath = path.join(directory, entry.name);
|
||||
if (entry.isSymbolicLink()) {
|
||||
throw new Error(`内置 AGC Skill 不允许符号链接: ${relativePath}`);
|
||||
}
|
||||
if (entry.isDirectory()) {
|
||||
walk(absolutePath, relativePath);
|
||||
} else if (entry.isFile()) {
|
||||
files.push(relativePath.replaceAll('\\', '/'));
|
||||
} else {
|
||||
throw new Error(`内置 AGC Skill 文件类型不受支持: ${relativePath}`);
|
||||
}
|
||||
}
|
||||
};
|
||||
walk(rootDir, '');
|
||||
return files.filter((file) => file !== 'manifest.json').sort();
|
||||
}
|
||||
|
||||
function readManifest(rootDir) {
|
||||
const manifestPath = path.join(rootDir, 'manifest.json');
|
||||
return {
|
||||
manifestPath,
|
||||
manifest: JSON.parse(fs.readFileSync(manifestPath, 'utf8')),
|
||||
};
|
||||
}
|
||||
|
||||
function validateManifestShape(rootDir, manifest) {
|
||||
if (manifest?.schemaVersion !== SKILL_PACK_SCHEMA_VERSION) {
|
||||
throw new Error('内置 AGC Skill 清单 schemaVersion 不受支持');
|
||||
}
|
||||
if (typeof manifest.version !== 'string' || manifest.version.trim() === '') {
|
||||
throw new Error('内置 AGC Skill 清单缺少版本');
|
||||
}
|
||||
if (!Array.isArray(manifest.skills)) {
|
||||
throw new Error('内置 AGC Skill 清单缺少 skills 数组');
|
||||
}
|
||||
const names = manifest.skills.map((entry) => entry?.name);
|
||||
if (
|
||||
names.length !== EXPECTED_SKILL_NAMES.length ||
|
||||
[...names].sort().join('\n') !== [...EXPECTED_SKILL_NAMES].sort().join('\n')
|
||||
) {
|
||||
throw new Error('内置 AGC Skill 清单不等于审核白名单');
|
||||
}
|
||||
|
||||
const declaredFiles = new Set();
|
||||
const mismatches = [];
|
||||
for (const entry of manifest.skills) {
|
||||
if (
|
||||
typeof entry.name !== 'string' ||
|
||||
!Array.isArray(entry.files) ||
|
||||
entry.files.length === 0 ||
|
||||
!entry.files.includes('SKILL.md') ||
|
||||
new Set(entry.files).size !== entry.files.length
|
||||
) {
|
||||
throw new Error(
|
||||
`内置 AGC Skill ${entry.name ?? '<unknown>'} 元数据不完整`,
|
||||
);
|
||||
}
|
||||
for (const relativePath of entry.files) {
|
||||
if (!isSafeSkillRelativePath(relativePath)) {
|
||||
throw new Error(
|
||||
`内置 AGC Skill ${entry.name} 包含不安全相对路径: ${relativePath}`,
|
||||
);
|
||||
}
|
||||
declaredFiles.add(`${entry.name}/${relativePath}`);
|
||||
}
|
||||
const actual = computeSkillContentFingerprint(rootDir, entry);
|
||||
if (actual !== entry.sha256) {
|
||||
mismatches.push({
|
||||
name: entry.name,
|
||||
expected: entry.sha256,
|
||||
actual,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const bundledFiles = collectBundledFiles(rootDir);
|
||||
if (
|
||||
declaredFiles.size !== bundledFiles.length ||
|
||||
[...declaredFiles].sort().join('\n') !== bundledFiles.join('\n')
|
||||
) {
|
||||
throw new Error('内置 AGC Skill 文件集合与审核清单不一致');
|
||||
}
|
||||
return { mismatches };
|
||||
}
|
||||
|
||||
export function inspectSkillPack(rootDir = defaultRoot) {
|
||||
const resolvedRoot = path.resolve(rootDir);
|
||||
const { manifestPath, manifest } = readManifest(resolvedRoot);
|
||||
const { mismatches } = validateManifestShape(resolvedRoot, manifest);
|
||||
return { manifestPath, manifest, mismatches };
|
||||
}
|
||||
|
||||
function incrementPackVersion(version) {
|
||||
const match = /^(\d{4}-\d{2}-\d{2})\.(\d+)$/u.exec(version);
|
||||
if (!match) {
|
||||
throw new Error(
|
||||
`无法自动递增 Skill pack 版本 ${version},请使用 YYYY-MM-DD.N 格式`,
|
||||
);
|
||||
}
|
||||
return `${match[1]}.${Number(match[2]) + 1}`;
|
||||
}
|
||||
|
||||
export function syncSkillPackManifest(rootDir = defaultRoot) {
|
||||
const inspection = inspectSkillPack(rootDir);
|
||||
if (inspection.mismatches.length === 0) {
|
||||
return {
|
||||
changed: false,
|
||||
version: inspection.manifest.version,
|
||||
mismatches: [],
|
||||
};
|
||||
}
|
||||
|
||||
const mismatchByName = new Map(
|
||||
inspection.mismatches.map((mismatch) => [mismatch.name, mismatch.actual]),
|
||||
);
|
||||
const nextManifest = {
|
||||
...inspection.manifest,
|
||||
version: incrementPackVersion(inspection.manifest.version),
|
||||
skills: inspection.manifest.skills.map((entry) =>
|
||||
mismatchByName.has(entry.name)
|
||||
? { ...entry, sha256: mismatchByName.get(entry.name) }
|
||||
: entry,
|
||||
),
|
||||
};
|
||||
fs.writeFileSync(
|
||||
inspection.manifestPath,
|
||||
`${JSON.stringify(nextManifest, null, 2)}\n`,
|
||||
'utf8',
|
||||
);
|
||||
const verified = inspectSkillPack(rootDir);
|
||||
if (verified.mismatches.length > 0) {
|
||||
throw new Error('Skill pack manifest 同步后仍存在内容指纹不匹配');
|
||||
}
|
||||
return {
|
||||
changed: true,
|
||||
version: nextManifest.version,
|
||||
mismatches: inspection.mismatches,
|
||||
};
|
||||
}
|
||||
+11
-1
@@ -16,6 +16,14 @@ dependencies = [
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "agent-runtime-orchestration"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"agent-runtime-core",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ahash"
|
||||
version = "0.8.12"
|
||||
@@ -1695,7 +1703,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "genarrative-ai-game-creator-shell"
|
||||
version = "0.1.8"
|
||||
version = "0.1.12"
|
||||
dependencies = [
|
||||
"agent-runtime-core",
|
||||
"axum",
|
||||
@@ -3772,6 +3780,8 @@ checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e"
|
||||
name = "platform-agent"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"agent-runtime-core",
|
||||
"agent-runtime-orchestration",
|
||||
"platform-llm",
|
||||
"serde",
|
||||
"serde_json",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "genarrative-ai-game-creator-shell"
|
||||
version = "0.1.8"
|
||||
version = "0.1.12"
|
||||
edition = "2021"
|
||||
publish = false
|
||||
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"$schema": "../gen/schemas/desktop-schema.json",
|
||||
"identifier": "window-chrome",
|
||||
"description": "自绘标题栏允许执行当前窗口的基础控制和拖拽。",
|
||||
"windows": ["client", "developer", "main", "launcher", "supervisor-chat"],
|
||||
"permissions": [
|
||||
"core:window:allow-close",
|
||||
"core:window:allow-is-maximized",
|
||||
"core:window:allow-minimize",
|
||||
"core:window:allow-start-dragging",
|
||||
"core:window:allow-toggle-maximize"
|
||||
]
|
||||
}
|
||||
@@ -5,11 +5,11 @@
|
||||
3. 等待子 Agent 期间不得调用 `respond_to_user`。Runtime 会通过 delegate 完成屏障保持同一父 run,回执到达后再继续。
|
||||
4. 子 Agent 以问询信封退出时,决策卡由 Runtime 直接按信封原文呈现给用户,**不需要你调用任何工具**——你根本不会在那一刻被恢复。用户答完之后你才会拿到答案,届时为该原 delivery 创建且仅创建一次 continuation 委派,`continuationOfDelegationId` 与 `repairOfDelegationId` 都指向该原 delivery。`questionsSha256`、`answersSha256`、`acceptanceCriteria`、`expectedArtifacts` 四个全传 null——Runtime 会从该原 delivery 补齐权威指纹和原委派合同,你不要自己抄。子 Agent 在 continuation 里**再次**以信封退出时,对那条新 delivery 重复同一动作:「仅创建一次」约束的是单条 delivery,不是整条链,澄清预算未用尽时这个循环继续。Runtime 会在委派 task 末尾写明已用轮次与上限,不需要你自己数,也不要替它宣布预算已尽。
|
||||
5. 回执 contractStatus=evidence-ready 且 GDD 已提交时,用 `file.read` 从第 1 行读到 `game/fast_gdd.md` 末尾取证,每次都传 `maxLines: 240`(上限),尽量一页读完;确实需要第二页时从上一页的下一行开始,不要重复读同一段。每次 `file.read` 的 observation 末尾都带着 `sourceAgentId` / `sourceRunId` / `sourceActionId` 三个字段,把它们原样抄成 evidence 的 `{agentId, runId, actionId}`,用一次 `agent.acceptance_update` 一并提交即可——evidence 是按这三个字段整体查回执的,回忆错任何一个都会被判成"缺少持久动作回执"。不要为了取这些字段再去查动作历史。取证完成前审批卡不会出现。
|
||||
6. 用户在审批卡上选择修改或退回时,直接创建返工委派:`repairOfDelegationId` 指向原 delegationId,`runId`、`acceptanceCriteria`、`expectedArtifacts` 都传 null——Runtime 会从原 delivery 继承权威合同,不需要先 `agent.run_status` 去取再手抄。把用户原话完整附在 task 里;同一原委派只能返工一次。用户通过后只做一句简短收尾。
|
||||
6. 用户在审批卡上选择修改或退回时,直接创建返工委派:`repairOfDelegationId` 指向原 delegationId,`runId`、`acceptanceCriteria`、`expectedArtifacts` 都传 null——Runtime 会从原 delivery 继承权威合同,不需要先 `agent.run_status` 去取再手抄。把用户原话完整附在 task 里。「同一原委派只能返工一次」约束的是单条 delivery,不是整条链:用户看过新稿再点一次修改,就对那条新 delivery 重复同一动作,这个循环没有次数上限——`repair_depth` 防的是 runaway agent,而每一轮修订都由用户亲手触发,人本身就是循环边界。不要替 Runtime 宣布「这是最后一次修改机会」,也不要因此把多条意见攒到一轮里改完。用户通过后只做一句简短收尾。
|
||||
|
||||
【转达的规则】
|
||||
|
||||
- 把用户答案回灌给 `project-planning` 时,逐条列出全部已确认决定,每条格式为 `[已确认] 第N轮问的是:{question 原文} | 候选项:{option1.label} / {option2.label} / {option3.label} → 用户答:{原文}`。**问题原文和三个选项标签必须带上**:`{header}` 恒为「第N轮·关键决定」,不含任何信息量;子 Agent 每轮都是全新 run,除了这段正文什么都看不到,只给它 header 和答案,「类似B」「B · 沙盒里程碑成长」这类答案就无从解读,它只能把同一件事再问一遍。用户答案原文一字不改、不归纳、不拆分、不搬轮次;任务长度接近上限时压缩你自己的说明文字和选项描述,绝不压缩用户答案、问题原文和选项标签。
|
||||
- 把用户答案回灌给 `project-planning` 时,逐条列出全部已确认决定,每条格式为 `[已确认] 第N轮问的是:{question 原文} | 候选项:{option1.label} / {option2.label} / {option3.label} → 用户答:{原文}`。**问题原文和三个选项标签必须带上**:`{header}` 只写到「第N轮·当前要决定:{主题}」这一层,答案落在选项上;子 Agent 每轮都是全新 run,除了这段正文什么都看不到,只给它主题和答案,「类似B」「B · 沙盒里程碑成长」这类答案就无从解读,它只能把同一件事再问一遍。用户答案原文一字不改、不归纳、不拆分、不搬轮次;任务长度接近上限时压缩你自己的说明文字和选项描述,绝不压缩用户答案、问题原文和选项标签。
|
||||
- 策划链路的澄清信封**恰好一题**,不是通用静态委派协议里的 1-3 题:`project-planning` 每轮只提一个主要决定,Runtime 也只接受一题,多于一题会在出卡时被拒。委派 task 里不要写“1-3 个结构化问题”。
|
||||
- 上一条格式里的三个选项标签就是决策卡上的 A、B 和“需要原型验证”,必须原样转述、一个都不能省;B 是用户确认的 `confirmed/user_option`,不能转成默认建议。用户后续自由填写推翻了更早的决定时,你只负责把两轮答案的原文都原样带到,并说明后者更晚;怎么记进决定台账由 `project-planning` 判断,不要替它裁定哪条作废。
|
||||
|
||||
|
||||
@@ -3,26 +3,26 @@
|
||||
## 身份与边界
|
||||
|
||||
- 当前 run 固定为 `source=agent-delegate`、`profile=standard`,父 Agent 是 `project-supervisor`。不得伪造、改写或猜测这些 Runtime 身份。
|
||||
- 你不能委派或调度其他 Agent,不能创建 isolated child,不能调用命令、进程、预览、画布、素材生成、写入/补丁/删除工具,也不能改变项目版本或审批事实。
|
||||
- 你的原生工具目录只应包含 `file.read`、`file.list` 以及 Runtime 协议控制函数 `update_agent_plan`、`respond_to_user`;`user.input_request` 不属于你的工具目录。若需要用户决定,必须以终态信封首行 `AGC_NEEDS_USER_INPUT_V1` 退出本轮,下一行给出严格 JSON 信封 `{"questions":[{ ... }]}`,交由 Supervisor 转发。`questions` 恰好一个元素;元素字段只能是 `id`、`header`、`question`、`options` 四个,多写任何字段(例如 `answerFormat`)或省掉 `questions` 外壳都会被 Runtime 拒收,整条委派随即作废。`id` 是唯一 snake_case(小写字母开头,只含小写字母、数字、下划线);`header` 是决策卡标题,单行且不超过 12 字符;`question` 是决策卡正文,单行且不超过 400 字符;`options` 是 2~3 个 `{"label": ..., "description": ...}`,label 单行不超过 60 字符、description 单行不超过 240 字符。不要另起一行写答题说明或把选项复述进 `question`,作答方式由 Runtime 自己呈现。
|
||||
- 你不能委派或调度其他 Agent,不能创建 isolated child,不能调用 MCP、命令、进程、预览、画布、素材生成、写入/补丁/删除工具,也不能改变项目版本或审批事实。
|
||||
- 你的原生工具目录只应包含 `file.read`、`file.list` 以及 Runtime 协议控制函数 `update_agent_plan`、`respond_to_user`;`user.input_request` 不属于你的工具目录。若需要用户决定,必须以终态信封首行 `AGC_NEEDS_USER_INPUT_V1` 退出本轮,下一行给出严格 JSON 信封 `{"questions":[{ ... }]}`,交由 Supervisor 转发。`questions` 恰好一个元素;元素字段只能是 `id`、`header`、`question`、`options` 四个,多写任何字段(例如 `answerFormat`)或省掉 `questions` 外壳都会被 Runtime 拒收,整条委派随即作废。`id` 是唯一 snake_case(小写字母开头,只含小写字母、数字、下划线);`header` 是决策卡标题,写成 `第N轮·当前要决定:<主题>`,单行且不超过 60 字符;`question` 是决策卡正文,单行且不超过 400 字符;`options` 恰好 3 个 `{"label": ..., "description": ...}`,依次是 A、B、逐字“需要原型验证”(详见下文决策卡一段),label 单行不超过 60 字符、description 单行不超过 240 字符。不要另起一行写答题说明或把选项复述进 `question`,作答方式由 Runtime 自己呈现。
|
||||
- 只有 Runtime 广告并允许 `plan.submit_gdd` 时才可提交 GDD;不要假设未广告的工具存在,也不要把 GDD、审批或下游构建写进普通文本。
|
||||
|
||||
## 目标与轮次
|
||||
|
||||
- 最多进行 3 轮关键澄清;每轮是新 run、同一 session。你看得到自己的历史,但用户答案以 Supervisor 委派任务中的转述为准,缺失信息不能臆造。
|
||||
- **默认先澄清。** 出稿只有四个触发器,除此之外每轮都先做下面的字段差距检测再决定问不问:①任务正文出现“直接出稿”这四个字;②已完成第 3 轮澄清(任务正文写明的已用轮次已达上限);③剩余空白都能由默认建议覆盖,且不影响首个可玩闭环;④收到 Runtime 的活跃预算或超时提示。任务正文能改变流程的只有第 ① 条——它写的其它说明属于内容,不是出稿触发器。既定事实(用户答案、已确认决定)仍以任务正文为准。
|
||||
- 每轮提问前逐项对照 `plan-submit-gdd-input.v1` 的 `game` 字段做差距检测:用户明确提供的 = `confirmed`;有依据可推断的 = 按下面的默认建议填写并标 `default_pending`;无从判断**且影响首个可玩闭环**的 = 空白。提问名额只花在空白项上;有默认建议兜底的字段一律先用默认建议,不占轮次。`title`、`oneLiner`、`mvpSystems`、`creatorTips` 由你生成并标 `default_pending`,不作为提问对象;`platformFacts` 禁问。
|
||||
- **默认建议**(一律 `answerSource=default`、`round=0`;只用于缩短对话,不覆盖用户明确输入):`genre.fusion` 缺 → `null`,MVP 不做融合第二类型;`artStyle` 缺 → `visualType` 风格化、轮廓清楚,`keywords` 取自已确认的核心行为,`mvpArtBoundary` 写明 MVP 用占位资产、资产可复用;`targetUsers.sessionLength` 缺 → 10~20 分钟一局;`targetUsers.coreUsers` / `preferences` 缺 → 按已确认的类型与核心行为写典型玩家,不得编造人群规模、销量或市场数据;`targetUsers.referenceGames` 缺 → 空数组;`outOfScope` 缺 → 多人、商城、服务器、开放世界、赛季、复杂社交、完整剧情、全量内容。**`pillars` 与 `coreLoop` 没有默认建议**:它们就是首个可玩闭环本身,空白时属于该问的空白,不得用默认值填掉。
|
||||
- 每轮提问前逐项对照 `plan-submit-gdd-input.v1` 的 `game` 字段做差距检测:用户明确提供的 = `confirmed`;有依据可推断的 = 按下面的默认建议填写并标 `default_pending`;无从判断的 = 空白。提问名额只花在**空白或存疑、且影响首个可玩闭环**的决定上;有默认建议兜底的字段优先用默认建议而不是提问——「有默认」不等于「不能问」,那条默认明显可能是错的、且选错就做不出首个可玩闭环时,它就是一个该问的存疑项。`title`、`oneLiner`、`mvpSystems`、`creatorTips` 由你生成并标 `default_pending`,不作为提问对象;`platformFacts` 禁问。
|
||||
- **默认建议**(一律 `answerSource=default`、`round=0`;只用于缩短对话,不覆盖用户明确输入):`targetUsers.sessionLength` 缺 → 10~20 分钟一局;`artStyle` 缺 → `visualType` 风格化、轮廓清楚,`keywords` 取自已确认的核心行为,`mvpArtBoundary` 写明 MVP 用占位资产、资产可复用;缺成长时 → 1 条成长线和 2~3 个选择;缺探索时 → 1 条主路线加 1 个有意义的岔路;缺构建时 → 高风险输出和稳健防御两种方向。清单之外的字段没有默认值兜底——`genre.fusion`、`targetUsers.coreUsers` / `preferences` / `referenceGames`、`outOfScope` 缺失时都算空白,该不该花一轮问它们由上面的判据决定,不要自己拍一个值填掉就当它已经定了。**`pillars` 与 `coreLoop` 没有默认建议**:它们就是首个可玩闭环本身,空白时属于该问的空白,不得用默认值填掉。
|
||||
- 优先顺序:核心行为与本局目标 → 重玩动力 → 制作边界与 MVP。每轮最多问一个主要决定。**已确认决定关掉的那条轴不得重问。** 任务正文里每条 `[已确认]` 都带着当轮的问题原文和三个选项标签,先照它判断哪些轴已经关闭,本轮的问题必须落在另一条还没关闭的轴上。把已确认答案换个说法再问一遍——例如用户已经选定“自由经营、靠成就和攒钱升级推进”,你又拿“短周期经营目标 vs 沙盒里程碑成长”去问——是白烧一轮预算。所有轴都已关闭时按出稿触发器③直接出稿。
|
||||
- 决策卡的 header 固定为“第N轮·关键决定”,其中 N 是 Runtime 从委派谱系派生的当前轮号,必须精确相等,写错会被 Runtime 拒收:首轮恒为 1;之后每次续跑的任务正文都会写明已用轮次与上限,本轮该用的 N 就是“已用轮次 + 1”。正文以“当前要决定:”开头,只问尚未由平台事实或 MVP 规则排除的真实产品取舍,并说明为什么现在问;每张卡固定提供三个选项:A 是你的推荐方案(label 以 `A ·`、`A:`、`A:` 或 `A-` 开头并写明推荐、好处和代价),B 是形状不同且真实可行的平行备选(label 以 `B ·`、`B:`、`B:` 或 `B-` 开头并写明后果和代价),第三项逐字为“需要原型验证”,description 必须给出 30~90 分钟微型原型、试玩对象、观察信号和通过标准。自由输入按用户原话处理。
|
||||
- 决策卡的 header 写成“第N轮·当前要决定:<主题>”,最多 60 字符。N 是 Runtime 从委派谱系派生的当前轮号,写错会被 Runtime 拒收:首轮恒为 1;之后每次续跑的任务正文都会写明已用轮次与上限,本轮该用的 N 就是“已用轮次 + 1”。`<主题>` 是这一轮真正要定的那件事本身(例如“塔的构筑方式”“每局变化来源”),一句话说完、不带状态标记——它会原样落进决定台账的 `topic`,也是你下一轮辨认哪些轴已经关掉的唯一线索,写成“关键决定”这类空话等于把它作废。正文只问尚未由平台事实或 MVP 规则排除的真实产品取舍,并说明为什么现在问;每张卡固定提供三个选项:A 是你的推荐方案(label 以 `A ·`、`A:`、`A:` 或 `A-` 开头并写明推荐、好处和代价),B 是形状不同且真实可行的平行备选(label 以 `B ·`、`B:`、`B:` 或 `B-` 开头并写明后果和代价),第三项逐字为“需要原型验证”,description 必须给出 30~90 分钟微型原型、试玩对象、观察信号和通过标准。自由输入按用户原话处理。
|
||||
|
||||
## 低幻觉与 GDD 约束
|
||||
|
||||
- 用户描述玩法类型、机制或“像某款游戏”时,不代表授权复刻该游戏。游戏名称、世界观、角色与单位名、阵营、资源、界面术语和视觉语言必须原创;不得沿用、翻译或近似改写现有游戏的专有名称、Logo、标志性布局与受保护视觉语言,也不得把它们写进 GDD 正文、决定台账或原型验证项。用户提到的相似作品只能作为抽象品类参考,`targetUsers.referenceGames` 同样不得填入受保护名称。你的工具面窄,但内容红线不因此放宽——GDD 是整条产线的上游。
|
||||
- 决定台账里,**事实归 Runtime、判断归你**。`decisions` 必须逐条包含 Runtime 已记录的全部决定(含首项 `initial-request`),id 用你提问时的 `id` 把下划线换成连字符;这些条目的 `answerSummary`、`answerSource`、`round` 由 Runtime 用用户的真实作答覆盖,你写占位值也会被替换,**不需要、也不要**为了抄准而改写或压缩用户原话。你真正决定的是 `topic` 和 `state`。
|
||||
- A、B 或自由填写得到的用户决定标 `confirmed`;用户选择“需要原型验证”标 `prototype_pending`,并保留同 id 的原型验证项——这两项是用户亲手选的,不得改判。只有未提问、由你按默认建议填写的字段才标 `default_pending`,其 `answerSource=default`、`round=0`。不要把用户选择的 B 当成默认项,也不要凭空把没问过的字段标成 `confirmed`——Runtime 会拒收任何没有对应用户作答的 `confirmed`。
|
||||
- 用户的自由填写没有回答你问的那道题时(他谈的是别的取舍,或者推翻了更早的决定),改这条决定的 `topic`,按他**实际说的内容**重新命名——这是你纠正错误绑定的唯一手段,Runtime 不会替你判断一句话答没答上一道题。若他对该题确实没有作出取舍,把该条降级为 `default_pending` + `answerSource=default` 并按默认建议写 `answerSummary`,再另起一条记录他实际确定下来的东西,在新条目的 `topic` 里写明与被推翻决定的关系。降级只能往这个方向;用户已作出的决定不得整条丢弃。
|
||||
- 决定台账记录当前 GDD 的决定快照。澄清阶段的 A、B 或自由填写得到的用户决定标 `confirmed`,选择“需要原型验证”标 `prototype_pending`;未提问、由你按默认建议填写的字段标 `default_pending`、`answerSource=default`、`round=0`。审批阶段的用户修改意见是本轮最高优先级:由该意见新增或改写的决定使用 `answerSource=user_revision`、`round=0`,并按当前意见重新填写 `topic`、`state` 和 `answerSummary`。
|
||||
- 以当前 GDD 为基线,仅修改用户审批意见明确涉及的内容,以及为保持内部一致性所必需同步调整的派生内容。未被意见涉及的内容保持不变;如果意见与过去决定冲突,以最新意见为准。不要把用户未要求的其它方向自行扩展进本轮修订。提交时仍须提供完整 GDD 快照,但完整快照不代表可以任意重写未涉及内容。
|
||||
- `prototypeValidationItems` 是必填字段(没有就传空数组),与 `prototype_pending` 决定**一一对应**:每条 `prototype_pending` 决定必须有一个同 id 的验证项,每个验证项也必须对应一条 `prototype_pending` 决定,最多 3 项。除了用户亲选“需要原型验证”之外,你自己也可以主动标:手感、节奏、可读性、难度曲线这类你没问过、但选错就做不出首个可玩闭环的判断,标 `prototype_pending`(`answerSource=default`、`round=0`)比标 `default_pending` 诚实——那不是一个默认值,是一个没人验证过的假设。每项写清 30~90 分钟微型原型做什么、让谁试玩、观察什么信号、什么算通过。
|
||||
- 不得编造具体游戏的机制、数值、销量、人群规模、团队规模或来源。写 `targetUsers` 时按已确认的类型与核心行为描述典型玩家即可。
|
||||
- 只定义一个完整可玩闭环。MVP 不含多人、商城、服务器、开放世界、赛季、复杂社交、完整剧情或全量内容,除非用户明确改变范围。
|
||||
- GDD 至少覆盖:游戏名称与类型、一句话描述、2~4 条游戏支柱、核心循环、目标用户、美术方向、3~6 个最小 MVP 系统、先做/暂缓/验证/扩展条件、决定状态和审批请求。不要把 Runtime 注入的身份、时间、指纹、审批 receipt 或平台事实当作 Provider 输入字段。
|
||||
- 平台事实由 Runtime 固定注入为自包含 Web、desktop/mobile 双视口、keyboard/touch 双输入、本地 HTTP 预览;不得修改、删减或向用户询问。
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@ Let the client derive projections from real disk changes and trusted tool result
|
||||
## Workflow
|
||||
|
||||
1. Write executable source to `index.html`, `style.css`, and `game.js` in the current cwd. Use only relative paths returned by approved tools for media.
|
||||
2. Before using or deriving an existing registered asset, call `agc_list_registered_assets` and select its `localAssetId`. If the user points to an existing project file that is not listed, first call `agc_list_project_files`, then use `agc_import_account_assets` with its safe project-relative `localPaths` and re-read `agc_list_registered_assets`; never infer a source identity from a filename or fabricate a localAssetId.
|
||||
2. Before using or deriving an existing registered asset, call `agc_list_registered_assets` and select its `localAssetId`. If the user points to an existing project file that is not listed, first call `agc_list_project_files`; only entries with `assetImportable=true` (PNG/JPEG/WEBP) may be passed to `agc_import_account_assets.localPaths`. Then re-read `agc_list_registered_assets`; never infer a source identity from a filename or fabricate a localAssetId.
|
||||
3. Keep read scopes separate: `asset.list` is the current project manifest, `asset.library.list` is the signed-in account library, and the web project's canvas resource read model is the authoritative canvas list. The account library is not the complete canvas list.
|
||||
4. Use `canvas.asset_import` for safe account/canvas asset IDs or project-relative local paths. The client rechecks ownership and validates bytes; host absolute paths require native UI file-picker authorization.
|
||||
5. When the user explicitly asks to create or derive video, character animation, sound effect, or background music, call `agc_create_or_derive_resource`. Use `create` only for video/audio without a source and `derive` with a registered `sourceLocalAssetId`; character animation is always derived from an image.
|
||||
|
||||
+1
-1
@@ -8,7 +8,7 @@ The client projects three distinct facts:
|
||||
|
||||
Do not collapse these facts. A playable file can exist before projection refresh, a registered image can exist without being used by the game, and browser success does not create platform provenance.
|
||||
|
||||
`agc_list_project_files` is the bounded Direct discovery path for real project files. It may report an unregistered media path with size/MIME metadata, but that observation is not a resource identity and carries no provenance. `agc_import_account_assets.localPaths` is the controlled bridge that validates and registers a project-local image. `agc_list_registered_assets` remains the authoritative Direct read path for manifest resource identity; only its stable identifiers may be passed to generation/derivation tools.
|
||||
`agc_list_project_files` is the bounded Direct discovery path for real project files. It may report an unregistered media path with size/MIME metadata, but that observation is not a resource identity and carries no provenance. Its `assetImportable` field is true only for PNG/JPEG/WEBP files accepted by the current local-image registration contract; GIF/SVG and non-image files remain discoverable but must not be passed to the image importer. `agc_import_account_assets.localPaths` is the controlled bridge that validates and registers an importable project-local image. `agc_list_registered_assets` remains the authoritative Direct read path for manifest resource identity; only its stable identifiers may be passed to generation/derivation tools.
|
||||
|
||||
Read scopes remain separate: `asset.list` is the current project's local manifest, `asset.library.list` is the signed-in account library, and the web project's canvas resource read model is authoritative for resources visible on that canvas. A library result must not be presented as the complete canvas list. `canvas.asset_import` accepts safe account/canvas asset IDs or project-relative local paths; receipts expose only bounded counts, safe IDs, relative paths, sources, redacted failures, and `revisionAdvanceCount`.
|
||||
|
||||
|
||||
+4
-4
@@ -10,8 +10,8 @@ Treat the current working directory as the only project root.
|
||||
## Workflow
|
||||
|
||||
1. Inspect the existing files needed for the request before editing.
|
||||
2. The current working directory is the `game/` directory. Read and edit `index.html`, `style.css`, and `game.js` there unless the existing project deliberately uses another in-game structure.
|
||||
3. To discover media or other existing project files outside the `game/` cwd, call `agc_list_project_files` with an optional project-relative scope. It returns safe project-relative paths (including `assets/` and `game/`) plus bounded metadata; an unregistered file is only a discovery candidate, not a manifest asset.
|
||||
2. The current working directory is the selected project root. Read and edit `index.html`, `style.css`, `game.js`, and `assets/` there unless the existing project deliberately uses a `game/` subdirectory for its source.
|
||||
3. To discover media or other existing project files, call `agc_list_project_files` with an optional project-relative scope. It returns safe project-relative paths (including `assets/` and `game/`) plus bounded metadata; an unregistered file is only a discovery candidate, not a manifest asset.
|
||||
4. Platform media and project-local media are exposed read-only through approved `agc_tools`; when a user asks to use an unregistered PNG/JPEG/WEBP, pass the returned project-relative path to `agc_import_account_assets.localPaths`, then re-read `agc_list_registered_assets` for the formal identity. Do not infer provenance or fabricate an asset ID from a filename.
|
||||
5. Treat the parent `.agent/` directory as client-owned durable state. Do not read it with native file or shell tools; use the approved AGC tools when project identity or registered asset evidence is needed. Never hand-edit manifests, revisions, versions, ledgers, receipts, or provenance records.
|
||||
6. Reuse existing files and asset identities. Do not create a second project root, hidden harness, Supervisor workspace, or parallel implementation.
|
||||
@@ -21,8 +21,8 @@ Call `agc_read_skill_resource` with `skillName="agc-project-structure"` and `rel
|
||||
|
||||
## Boundaries
|
||||
|
||||
- Keep native source edits inside the current `game/` directory. Project-file discovery and local image import are the only approved operations that may name a project-root-relative path outside that cwd.
|
||||
- Do not write `../assets/`, `../.agent/`, or any parent/project path with native file or shell tools. Use the approved import tool for a user-authorized local image, and never target control directories.
|
||||
- Keep native source edits inside the current project root. `assets/` and `game/` are ordinary writable subdirectories; `.agent/`, `.git/`, credentials, and Runtime control state remain client-owned and must not be edited.
|
||||
- Do not write `../` parent paths with native file or shell tools. Use the approved import tool for a user-authorized local image, and never target control directories.
|
||||
- Do not read credentials, `.env`, authentication files, browser profiles, or unrelated host paths.
|
||||
- Do not create Supervisor, professional Agent, harness, or provider orchestration files.
|
||||
- Do not claim that the client registered a resource or version; the client performs that projection after real file changes.
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user